Search code examples
javascriptnestjsdtoclass-validator

NestJS: class-validator Not Throwing Validation Errors for Casted Objects


To start with the example code:

// anniversarycredits.dto.ts

export class AnniversaryCreditsDto {
  @IsNumber()
  @ApiProperty({
    description: 'The amount of credits to award on anniversary.',
  })
  'creditAmount': number;

  @IsString()
  @IsOptional()
  @ApiProperty({ description: 'The name (appearance only) of this benefit.' })
  'name': string;

  @IsString()
  @IsOptional()
  @ApiProperty({
    description: 'The description of this benefit (appearance only).',
  })
  'description': string;

  @Equals('ANNIVERSARY_CREDITS')
  @IsOptional()
  @ApiProperty({
    description: 'This is an internal identifier, leave default.',
  })
  'type' = 'ANNIVERSARY_CREDITS';
}
// benefits.controller.ts (snippet)

@Patch()
  update(
    @Req() req: any,
    @Body()
    updateBenefitData: any,
  ): Promise<ResponseDto> {
    switch (updateBenefitData.type) {
      case 'ANNIVERSARY_CREDITS':
        return this.benefitsService.update(
          req._merchantId,
          updateBenefitData as AnniversaryCreditsDto,
        );

As you can see, I have class-validator tags set up on the dto that I am casting to. class-validator works in all other cases except for cases I am casting. In casting, it throws no errors and allows any input.

I have tried searching around for answers, can't seem to find anyone that ran into this same issue. I looked into the possibility of constructing all of the objects rather than casting, but it didn't seem like validator runs when constructed with new. Any input is appreciated, thanks.


Solution

  • class-validator, as its name suggests, works on class instances. Nest makes it seemingly work on regular objects through some clever use of parameter metadata reflection and its ValidationPipe which uses class-transformer to take the incoming object and translate it into a class instance. Then the instance has validate (from class-validator) called on it, and the instance has its property metadata read so that each property can be properly validated.

    Casting, as you're doing here, is only telling the compiler "Look, I know that this variable is this value, trust me". It has no runtime meaning, and all you have is an object at that point. Similarly, as you use any type for the @Body() parameter, the ValidaitonPipe looks at the metadata and goes "Dunno what this is" and returns the value directly, as there's no instance it can knowingly turn it in to for validation.

    If you need to keep with this any approach, 1) think about using unknown instead,and 2) translate the object into an instance of the AnniversaryCreditsDto and then use validate from class-validator to see if the object has any errors according to your decorations.