class-validator icon indicating copy to clipboard operation
class-validator copied to clipboard

fix: @ValidateIf receives transformed nested object instead of POJO

Open tsamsiyu opened this issue 6 years ago • 10 comments

I have the following class definitions:

class Field {
    @IsString()
    readonly name: string;

    @IsString()
    @IsIn(['values', 'numbers', 'dates'])
    readonly type: string;

    @ValidateNested()
    @Type(() => Rule)
    @IsNotEmpty()
    readonly rules: Rule[];
}

class Rule {
    @ValidateIf((a, b) => { console.log(b); return true; })
    @IsDate()
    readonly fromDate: Date;

    @IsDate()
    readonly toDate: Date;

    @IsNumber()
    readonly fromNumber: number;

    @IsNumber()
    readonly toNumber: number;

    @IsString()
    readonly label: string;

    @IsNumber()
    readonly value: number;

    @IsNumber()
    readonly rate: number;
}

Validating properties of class Rule directly depends on value of property type of Field class object but in callback of ValidateIf decorator I've got object of Rule class. So it makes impossible to achieve conditional validation on nested objects.

tsamsiyu avatar Nov 04 '18 19:11 tsamsiyu

Anyone on this?

ChrisGatzo avatar Dec 07 '18 19:12 ChrisGatzo

+1 Anyone on this?

hlibco avatar Dec 25 '18 02:12 hlibco

@NoNameProvided I'm considering to submit a PR for this issue, is that okay? I'd need some guidance tho. I've explored the internals and an outlined solution can be:

  1. Add context to the signature of the ValidateIf decorator (decorators.ts).
export function ValidateIf(condition: (object: any, value: any, context: any) => boolean, validationOptions?: ValidationOptions)
  1. Pass the context to the validator in the conditionalValidations() (ValidationExecutor.ts):
.map(metadata => metadata.constraints[0](object, value, metadata.context))

In the use-case given by @tsamsiyu, the ValidateIf decorator should be used like the following, in case we want to validate fromDate ONLY IF the property name in the Field class is provided:

class Rule {
    @ValidateIf((obj, value, context) => { !!return context.Field.name })
    @IsDate()
    readonly fromDate: Date;
}

Does it look the right way to approach this problem?

hlibco avatar Dec 26 '18 05:12 hlibco

After digging a bit more in the internals, I realized that the approach I mentioned above would not work.

@NoNameProvided I submitted a PR https://github.com/typestack/class-validator/pull/301 with tests included. I'd appreciate if you can check it out and give your feedback. Btw, congrats for the project, I'm a big fan of it.

@ChrisGatzo and @tsamsiyu I hope this solves the problem.

hlibco avatar Dec 26 '18 07:12 hlibco

You can use groups in this case:

class Field {
    @IsString({ always: true })
    name: string;

    @IsString({ always: true })
    @IsIn(['values', 'numbers', 'dates'])
    type: 'values' | 'numbers' | 'dates';

    @ValidateNested({ always: true })
    @Type(() => Rule)
    @IsNotEmpty({ always: true })
    rules: Rule[];
}

class Rule {
    
    @IsDate({ groups: ['dates']})
    fromDate: Date;

    @IsNumber({}, { groups: ['numbers']})
    someNumber: number;
}

const t = new Field();
t.name = 'supertest';
t.type = 'numbers';
t.rules = [new Rule()];

console.log(validateSync(t, {groups: [t.type]})); // only someNumber field error is provided

https://stackblitz.com/edit/class-validator-gh278?file=index.ts

vlapo avatar Mar 26 '20 20:03 vlapo

@vlapo I created a PR for this since I frequently run into it validating config files I get from customers. Please let me know if you see any issues or any better ways for me to accomplish my goal of supporting shorthand syntax as well as nested configs (ideally without transforming first since I want to be able to give very specific errors to users).

davidthor avatar Apr 19 '20 13:04 davidthor

The first comment mentions the decorator receives a transformed nested class instead of a plain object. If that is the case, then it's a bug and should be fixed.

Note: It seems there is a test for passing in a class instance, so this was intended, however, it doesn't make sense as on an invalid object the validation will fail on the nested object before the ValidateIf decorator is reached.

NoNameProvided avatar Aug 04 '20 21:08 NoNameProvided

You can use groups in this case:

https://stackblitz.com/edit/class-validator-gh278?file=index.ts

This doesn't really work in the case of using with Nestjs ValidationPipe. Would appreciate if we can pass context to nested objects instead.

hazz1925 avatar Nov 12 '20 02:11 hazz1925

Anyone knows how to make it work with global ValidationPipe of NestJs?

brojd avatar Nov 26 '20 11:11 brojd

Anyone knows how to make it work with global ValidationPipe of NestJs?

https://stackblitz.com/edit/class-validator-gh278-9bsxzm?file=index.ts

marcoscoelho avatar May 05 '22 04:05 marcoscoelho

2 years passed and there is no solution for this, we have to create a new feature in the form of context

LuckyArdhika avatar Oct 24 '22 06:10 LuckyArdhika

Anyone knows how to make it work with global ValidationPipe of NestJs?

https://stackblitz.com/edit/class-validator-gh278-9bsxzm?file=index.ts

not working, groups has no effect

LuckyArdhika avatar Oct 24 '22 06:10 LuckyArdhika