Class validator: Use class member as decorator argument
Asked Answered
V

3

6

I have a signup DTO where one member is dependent of another.

The IsPostalCode on zip needs to know the countryCode/locale, which is one of the other class members.

Is it possible to use a class member as decorator argument?

import {
  IsEmail,
  IsISO31661Alpha2,
  IsPostalCode,
  IsString
} from "class-validator"

export class SignupDto {
  @IsEmail()
  email: string

  @IsString()
  password: string

  @IsISO31661Alpha2()
  countryCode: string

  // Something like this
  @IsPostalCode(this.countryCode)
  zip: string
}
Vampirism answered 16/2, 2021 at 12:36 Comment(0)
D
4

You can use the Validate decorator and create a custom validation method. For example, assuming you have properties zip and countryCode on your dto:

    @ValidatorConstraint({ name: 'isPostalCodeByCountryCode', async: false })
    class IsPostalCodeByCountryCode implements ValidatorConstraintInterface {
      validate(zip: string, args: ValidationArguments): boolean {
        return isPostalCode(zip, (args.object as any).countryCode);
      }
    
      defaultMessage(args: ValidationArguments): string {
        return `Invalid zip "${(args.object as any).zip}" for country "${(args.object as any).countryCode}"`;
      }
    }

Usage:

  @Validate(IsPostalCodeByCountryCode)
  public zip: string;
Devy answered 18/4, 2022 at 20:7 Comment(0)
P
9

You can create a custom validator like below:

import {
  ValidationOptions,
  registerDecorator,
  ValidationArguments,
  buildMessage,
} from 'class-validator';
/**
* Install validator package from npm. class-validator uses validator under the 
* hood
*/
import {isISO31661Alpha2,isPostalCode} from 'validator';

export function IsPostalCodeOf(
  property: string,
  validationOptions?: ValidationOptions,
) {
  // eslint-disable-next-line @typescript-eslint/ban-types
  return function(object: Object, propertyName: string) {
    registerDecorator({
      name: 'isPostalCodeOf',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [property],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          // Getting the country code field from the argument.
          // countryCode field from SignupDto
          const [countryCodeField] = args.constraints;
          // Getting the value of the countryCode Field
          const countryCode = (args.object as any)[countryCodeField];
          // Checking if the country code is valid even though it is checked 
          // at class level 
          if (!isISO31661Alpha2(countryCode)) {
          // Invalid county code
            return false;
          }
          // Checks if the value (zip) belongs in the extracted countryCode 
          // field
          return isPostalCode(value,countryCode);
        },
        // Specifiy your error message here.
        defaultMessage: buildMessage(
          eachPrefix =>
            `${eachPrefix} $property must be a valid postal 
             code in the specified country `,
          validationOptions,
        ),
      },
    });
  };
}

Usage:

export class SignupDto {
  @IsEmail()
  email: string

  @IsString()
  password: string

  @IsISO31661Alpha2()
  countryCode: string

  @IsPostalCodeOf('countryCode')
  zip: string
}
Perlie answered 18/2, 2021 at 3:28 Comment(0)
D
4

You can use the Validate decorator and create a custom validation method. For example, assuming you have properties zip and countryCode on your dto:

    @ValidatorConstraint({ name: 'isPostalCodeByCountryCode', async: false })
    class IsPostalCodeByCountryCode implements ValidatorConstraintInterface {
      validate(zip: string, args: ValidationArguments): boolean {
        return isPostalCode(zip, (args.object as any).countryCode);
      }
    
      defaultMessage(args: ValidationArguments): string {
        return `Invalid zip "${(args.object as any).zip}" for country "${(args.object as any).countryCode}"`;
      }
    }

Usage:

  @Validate(IsPostalCodeByCountryCode)
  public zip: string;
Devy answered 18/4, 2022 at 20:7 Comment(0)
V
-3

Decorators aren't able to inherently use class properties inside of them, due to how decorators work in typescript. You could create a custom validation decorator to read the other properties of the class and validate the zip property correctly, but that could be a bit of work to get going.

So to answer your question:

Is it possible to use a class member as decorator argument?

No, it is not.

Vilhelmina answered 16/2, 2021 at 19:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.