typestack / class-validator

Decorator-based property validation for classes.
MIT License
11.03k stars 800 forks source link

value is getting undefined in case of custom validation for file #1917

Open nitinmagdumvista opened 1 year ago

nitinmagdumvista commented 1 year ago

i want to validate the file type also if the file is provided or not i am writing custom validator but in both case with and without file its giving me value undefined

// ============ custom validator

`import { Injectable } from '@nestjs/common'; import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, } from '@nestjs/class-validator'; import { ValidatorConstraintInterface } from '@nestjs/class-validator/types/validation/ValidatorConstraintInterface';

// create Constraint @ValidatorConstraint({ async: true }) @Injectable() export class ValidateEnumContsraint1 implements ValidatorConstraintInterface { private error: string;

defaultMessage(validationArguments?: ValidationArguments): string { return this.error; }

validate(value: any, validationArguments?: ValidationArguments): Promise | boolean { try { console.log('value', value); console.log('validationArguments', validationArguments); return true; } catch (e) { this.error = Error in input value for ${validationArguments.property} - ${JSON.stringify(e)}; return false; } } }

// create decorator export function ValidateFileDecorator(enumName: any, validationOptions?: ValidationOptions) { return function (object: any, propertyName: string) { registerDecorator({ target: object.constructor, propertyName: propertyName, options: validationOptions, constraints: [enumName], validator: ValidateEnumContsraint1, }); }; } `

//=================== validatore used inside DTO

@ValidateFileRequiredDecorator() @ApiProperty({ type: 'string', format: 'binary', required: true }) readonly file: Express.Multer.File;

OguzBey commented 1 year ago

+1 This problem...

"this" object all properties set to undefined when use new keyword.

OguzBey commented 1 year ago

version: 0.14.0

example code:


export class ClassValidatorDTO {
  constructor(data: Record<string, any>) {
    Object.assign(this, { ...data });
  }

  checkErrors() {
    let errors = validateSync(this, { whitelist: true });
    return errors;
  }
}
export class UserCreateDTO extends ClassValidatorDTO {
  @Length(3, 20)
  username: string;
  @Length(8, 32)
  password: string;
  @IsEmail()
  email: string;
  @Length(3, 20)
  name: string;
  @Length(3, 20)
  surname: string;
}
let reqBody = new UserCreateDTO(data); // reqBody all props undefined now
console.dir({...reqBody}, {depth: null})
let errors = reqBody.checkErrors();
if (errors.length) console.log(errors)
console.dir({...reqBody})
OguzBey commented 1 year ago

I think it's a problem with typescript compile settings. target

I just got this error in es2022.

braaar commented 1 year ago

I can't reproduce whatever issue you seem to be describing here. Here's my attempt at reproducing your issue, and everything seems to work fine (your validator is just console.logging the field value, so there's really nothing that can go wrong there):

import { plainToInstance } from "class-transformer";
import {
  registerDecorator,
  validate,
  ValidationArguments,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from "class-validator";

// create Constraint
@ValidatorConstraint({ async: true })
export class ValidateEnumContsraint1 implements ValidatorConstraintInterface {
  private error: string;

  defaultMessage(validationArguments?: ValidationArguments): string {
    return this.error;
  }

  validate(
    value: any,
    validationArguments?: ValidationArguments
  ): Promise<any> | boolean {
    try {
      console.log("value", value);
      console.log("validationArguments", validationArguments);
      return true;
    } catch (e) {
      this.error = `Error in input value for ${
        validationArguments.property
      } - ${JSON.stringify(e)}`;

      return false;
    }
  }
}

// create decorator
export function ValidateFileDecorator(
  enumName: any,
  validationOptions?: ValidationOptions
) {
  return function (object: any, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      constraints: [enumName],
      validator: ValidateEnumContsraint1,
    });
  };
}

class MyClass {
  @ValidateFileDecorator("banana")
  file: unknown;
}

const plain = { file: "this is a file I guess" };

const instance = plainToInstance(MyClass, plain);

const errors = await validate(instance);
console.log(errors); // []
ai-leonid commented 7 months ago

I think it's a problem with typescript compile settings. target

I just got this error in es2022.

I face up with same problem. And you are right! Option "target": "es2022" in tsconfig is reason. I tried downgrade to "es2021" - and undefined is gone.

Steps to reproduce with some controller file:

import { Controller, Post, Body } from '@nestjs/common';
import { IsOptional } from 'class-validator';

export class TestDto {
  @IsOptional()
  option1: string;

  @IsOptional()
  option2: string;
}

@Controller('test')
export class TestController {
  @Post('/')
  createTest(@Body() body: TestDto) {
    /* Input body value is 
      {
         option1: 'val1'
      }
     */
    console.log('----------Log test dto');
    console.log(body);
    // tsconfig with "target": "es2022" result is: TestDto { option1: 'val1', option2: undefined }
    // tsconfig with "target": "es2021" result is: TestDto { option1: 'val1' }

    return body;
  }
}

Some package versions: nodejs v18.19.1 "class-validator": "^0.14.1", "@nestjs/common": "^9.3.12", "typescript": "~5.3.3"

Compiler options

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2022", // or es2021
    "sourceMap": true,

    "outDir": "./dist",
    "baseUrl": "./",

    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,

    "incremental": true,
    "skipLibCheck": true,

    "strict": true
  },
  "exclude": [
    "node_modules",
  ]
}
ai-leonid commented 7 months ago

So reason is useDefineForClassFields option in tsconfig. For target ES2022 or higher this option is "useDefineForClassFields": true by default otherwise false. After changing tsconfig to this:

{
  "compilerOptions": {
    //....some options
    "module": "commonjs",
    "target": "es2022",
    "useDefineForClassFields": false,
    //....some options
}

Problem is gone!