0

I'm building an accounting application using NestJS. I have a DTO called CreateTransactionDto, and I want certain fields to be required or forbidden based on the value of transactionType. For example:

If transactionType = SALE, then fields like quantity and unitPrice must be provided, but receivedAmount must not be included.

If transactionType = PAYMENT, then receivedAmount is required, and quantity and similar fields must not be included.

Here’s how I’ve defined validation for the receivedAmount field:

// dto
@ApiProperty({
  description: 'Type of the transaction',
  enum: TransactionType,
})
@IsNotEmpty()
@IsEnum(TransactionType)
transactionType: TransactionType;

@ValidateIf((o: CreateTransactionDto) => o.transactionType === TransactionType.PAYMENT)
@IsNotEmpty({
  message: 'Received amount is required for PAYMENT transactions',
})
@IsNumber()
@ValidateIf((o: CreateTransactionDto) => o.transactionType === TransactionType.SALE)
@IsEmpty({
  message: 'Received amount should not be provided for SALE transactions',
})
receivedAmount?: number;

I'm also using the global ValidationPipe with the following configuration:

// main.ts
app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: true,
    forbidUnknownValues: true,
    stopAtFirstError: false,
  }),
);

However, when I send the following JSON payload, no validation error occurs:

{
  "customerId": 1,
  "transactionType": "SALE",
  "transactionDate": "2023-10-01",
  "description": "Sale of goods",
  "quantity": 10,
  "quantityUnit": "pcs",
  "unitPrice": 50,
  "receivedAmount": 500
}

Even though transactionType is SALE, and receivedAmount should not be included, the request goes through without validation errors.

My question is: How can I correctly use ValidateIf to conditionally validate or reject fields based on other field values? Is there something I'm missing or doing wrong?

1 Answer 1

0

@ValidateIf and Inside It Handle the Logic

Instead of layering multiple @ValidateIfs and validators, consolidate validation using a single custom @ValidateIf() for each conditional branch.

@ValidateIf((o) => o.transactionType === TransactionType.PAYMENT)

@IsNotEmpty({ message: 'Received amount is required for PAYMENT transactions' })

@IsNumber()

receivedAmount?: number;

@ValidateIf((o) => o.transactionType === TransactionType.SALE && o.receivedAmount !== undefined)

@IsEmpty({ message: 'Received amount should not be provided for SALE transactions' })

receivedAmount?: number;

Create a custom validator for receivedAmount:

import {
  registerDecorator,
  ValidationOptions,
  ValidationArguments,
} from 'class-validator';

export function IsValidReceivedAmount(validationOptions?: ValidationOptions) {
  return function (object: any, propertyName: string) {
    registerDecorator({
      name: 'isValidReceivedAmount',
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          const obj = args.object as any;
          if (obj.transactionType === 'PAYMENT') {
            return typeof value === 'number' && value !== null;
          } else if (obj.transactionType === 'SALE') {
            return value === undefined || value === null;
          }
          return true;
        },
        defaultMessage(args: ValidationArguments) {
          const obj = args.object as any;
          if (obj.transactionType === 'SALE') {
            return 'receivedAmount should not be provided for SALE transactions';
          }
          if (obj.transactionType === 'PAYMENT') {
            return 'receivedAmount is required for PAYMENT transactions';
          }
          return 'Invalid value for receivedAmount';
        },
      },
    });
  };
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.