typestack / class-validator

Decorator-based property validation for classes.
MIT License
10.98k stars 793 forks source link

question: validate based on different type in model #566

Closed HunterJS-bit closed 4 years ago

HunterJS-bit commented 4 years ago

Hi Im working with TypeORM and class validation,, and I would like to provide different to field based on other field value. So to explain, here is how my DTO model looks like this:

import { IsString, IsOptional, IsNumber, IsEnum, IsObject, IsBoolean, ValidateNested } from 'class-validator';

export enum AttributeTypes {
    DATE = 'DATE',
    TIME = 'TIME',
    NUMBERS = 'NUMBERS',
}

export class BaseValidation {
    @IsOptional()
    @IsBoolean()
    required: boolean;
}

export class modelCreate {
    @IsOptional()
    @IsNumber()
    id: number;

    @IsOptional()
    @IsString()
    label: string;

    @IsOptional()
    @IsEnum(AttributeTypes)
    type: AttributeTypes;

    @IsOptional()
    @IsObject()
    @ValidateNested()
    validation: BaseValidation;
}

he problem here is that I have this field: validation in modelCreate, & that field is an object and can have multiple properties & can look like this in db:

validation: {
   required: true,
   text: 2
}

or it can look like this

 validation: {
       required: false,
       number: 1,
       maxNumber: 10
    }

and that would depend on type property of modelCreate, because if type is 'TIME', I would like to have validation for this:

BaseValidation {
    @IsBoolean()
     required: true,
    @IsString()
    text: 2
}

and if type is 'NUMBERS', I would like to have validation like this

 BaseValidation {
           @IsBoolean()
           required: boolean,
           @IsNumber()
           number: number,
           @IsNumber()
           maxNumber: number
        }

o the question is how would I toogle different classes in validation field based on type field value in class validator, and is that even possible ?

vlapo commented 4 years ago

You can use groups - https://github.com/typestack/class-validator#validation-groups - example https://github.com/typestack/class-validator/issues/278#issuecomment-604675151

Or create different classes for all types. E.g.NumbersValidation, TimeValidation, DateValidation

export class modelCreate {
    @IsOptional()
    @IsNumber()
    id: number;

    @IsOptional()
    @IsString()
    label: string;

    @IsOptional()
    @IsEnum(AttributeTypes)
    type: AttributeTypes;

    @IsOptional()
    @IsObject()
    @ValidateNested()
    validation: TimeValidation | NumbersValidation | DateValidation;
}
HunterJS-bit commented 4 years ago

@vlapo Could you explain me what in you example is TimeValidation, NumbersValidation & DateValidation how would those classes look how would I know which one to xonnect to type ??? Second Problem is that Im using NestJS with pypes I do not see how can I pass the groups into my validation pypes like bellow

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}
vlapo commented 4 years ago

So you can use class-transformer discriminator feature and create different classes (with different validations) for different validation types. - https://github.com/typestack/class-transformer#providing-more-than-one-type-option

export enum AttributeTypes {
    DATE = 'DATE',
    TIME = 'TIME',
    NUMBERS = 'NUMBERS',
}

export class TimeValidation {
    @IsBoolean()
    required: true,
    @IsString()
    text: 2
}

export class NumbersValidation {
    @IsBoolean()
    required: boolean,
    @IsNumber()
    number: number,
    @IsNumber()
    maxNumber: number
}

export class DateValidation {
    // whatever properties you need here
}

export class modelCreate {
    @IsOptional()
    @IsNumber()
    id: number;

    @IsOptional()
    @IsString()
    label: string;

    @IsOptional()
    @IsEnum(AttributeTypes)
    type: AttributeTypes;

    @IsOptional()
    @IsObject()
    @ValidateNested()
    @Type(() => TimeValidation, {
        discriminator: {
            property: "__type",
            subTypes: [
                { value: TimeValidation, name: AttributeTypes.DATE },
                { value: NumbersValidation, name: AttributeTypes.NUMBERS },
                { value: DateValidation, name: AttributeTypes.TIMES }
            ]
        }
    })
    validation: TimeValidation | NumbersValidation | DateValidation;
}
HunterJS-bit commented 4 years ago

@vlapo For some reason is not working I always get TimeValidation executed :/....maybe because __type is not a property of validation but property of modelCreate ??

vlapo commented 4 years ago

Did you try property: "type" ? My example was only copy&paste from class-transformer doc :)

HunterJS-bit commented 4 years ago

Yes, but type its working like that if type is property inside of validation object, but for me the type should be on modelCreate :), nevermind this doesnt concerne class validator, @vlapo thank you ver much , closing issue.

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.