class-validator relational validation with custom error messages
Asked Answered
M

1

5

so I was building an API where by user gives us type and value in the request body, type could be CNIC, EMAIL, MOBILE

Now based on the type I have to validate the value like if EMAIL is valid or if MOBILE is valid etc.

So as we can see that value field depends on the type field to validate it.

I need a way to handle this using class-validator validationPipe.

Melda answered 22/3, 2022 at 9:20 Comment(0)
M
8

So at first I quickly opened the docs and started looking up for advance usage and I came to know that custom decorator would definitely help me and it did using bellow example.

validate-value-by-type.decorator.ts

import {
  registerDecorator,
  ValidationOptions,
  ValidationArguments,
  isEmail,
} from 'class-validator';
import { CA_DetailsTypes } from './models';

export function ValidateByAliasType(
  property: string,
  validationOptions?: ValidationOptions,
) {
  // eslint-disable-next-line @typescript-eslint/ban-types
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'validateByAliasType',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [property],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          if (relatedValue === CA_DetailsTypes.EMAIL) {
            return isEmail(value) && value.length > 5 && value.length <= 99;
          }
          if (relatedValue === CA_DetailsTypes.CNIC) {
            return value.length === 13;
          }
          if (relatedValue === CA_DetailsTypes.MOBILE) {
            return value.length === 11;
          }
          if (relatedValue === CA_DetailsTypes.TXT) {
            return value.length > 3 && value.length <= 99;
          }
          return false;
        },
      },
    });
  };
}

client.request.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty } from 'class-validator';

export enum CA_DetailsTypes {
  'CNIC' = 'CNIC',
  'MOBILE' = 'MOBILE',
  'EMAIL' = 'EMAIL',
  'TXT' = 'TXT',
}

export class CA_FetchDetails_DTO {
  @ApiProperty({
    example: 'MOBILE',
    type: 'enum',
    enum: CA_DetailsTypes,
  })
  @IsNotEmpty()
  @IsEnum(CA_DetailsTypes)
  type: CA_DetailsTypes;

  @ApiProperty({ example: '03070000002' })
  @ValidateByAliasType('type')
  value: string;
}

So with the above example I'm successfully able to validate value by type, but still I'm unable to customize the error message based on the type 😭.

😢 So after a day of research I lose all my hope and decided to use another library for this complex validation and I did so but there was a drawback with that library and I ended up coming back to the class-validator and after a lot of research I found a way to customize the error message based on the type.

So here is the code for customizing error message based on the type and I used defaultMessage() to customize it.

validate-value-by-type.decorator.ts

import {
  registerDecorator,
  ValidationOptions,
  ValidationArguments,
  isEmail,
} from 'class-validator';
import { CA_DetailsTypes } from './models';

export function ValidateByAliasType(
  property: string,
  validationOptions?: ValidationOptions,
) {
  // eslint-disable-next-line @typescript-eslint/ban-types
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'validateByAliasType',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [property],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          if (relatedValue === CA_DetailsTypes.EMAIL) {
            return isEmail(value) && value.length > 5 && value.length <= 99;
          }
          if (relatedValue === CA_DetailsTypes.CNIC) {
            return value.length === 13;
          }
          if (relatedValue === CA_DetailsTypes.MOBILE) {
            return value.length === 11;
          }
          if (relatedValue === CA_DetailsTypes.TXT) {
            return value.length > 3 && value.length <= 99;
          }
          return false;
        },
        defaultMessage(args?: ValidationArguments) {
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          switch (relatedValue) {
            case CA_DetailsTypes.EMAIL:
              return 'Please enter valid email!';

            case CA_DetailsTypes.MOBILE:
              return 'Please enter valid mobile!';

            case CA_DetailsTypes.CNIC:
              return 'Please enter valid CNIC!';

            default:
              return 'Invalid value!';
          }
        },
      },
    });
  };
}

🤗 And finally it's done!

Melda answered 22/3, 2022 at 9:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.