NestJS - Validating body conditionally, based on one property
Asked Answered
M

3

17

I'm trying to find a nice way to validate a body using DTO (using the brilliant class-validator and class-transformer libraries). It works really well, even for nested structures but in my case I'd like to have the body property based on some conditions.

Example that will probably help to understand:

Let's imagine my body should always have selectedCategory. Based on that field, the content could either be from category 1, which contains prop1 OR from category 2, which contains prop2.

I do not want to allow a null for both of them, I really want to have to either have prop1 defined or prop2 based on the selectedCategory.

I think that I could use a pipe, but then how can I specify the correct DTO to use?

I've built a "base" class with all the common properties and few other classes that inherit from it.

I could instantiate the pipe manually based on the property selectedCategory, that'd be ideal but I have no clue what to pass as a second argument of the pipe (metadata).

Thanks for your help.

Monseigneur answered 5/1, 2019 at 22:47 Comment(2)
Any ideas on how to do this if your variable is defined in the request (as a param) rather than a variable in the body?Crisper
I'm afraid not, I haven't been working with NestJS for a while now. If you find out it might be interesting to post an answer here though :)Monseigneur
S
21

Have you tried using groups? Instead of having multiple DTOs, you just create one DTO. Every property is assigned to one or multiple groups:

@Min(12, {groups: ['registration', 'update']})
age: number;
@Length(2, 20, {groups: ['registration']})
name: string;

You can then conditionally pass the groups to class transformer / validator:

@Injectable()
export class ConditionalValidationPipe implements PipeTransform {
  async transform(entity: any, metadata: ArgumentMetadata) {
    // Dynamically determine the groups
    const groups = [];
    if (entity.selectedCategory === 1) {
      groups.push('registration');
    }

    // Transform to class with groups
    const entityClass = plainToClass(EntityDto, entity, { groups })

    // Validate with groups
    const errors = await validate(entityClass, { groups });
    if (errors.length > 0) {
      throw this.createError(errors);
    }
    return entityClass;
  }
}
Sile answered 5/1, 2019 at 23:18 Comment(6)
where do you have this.createError defined?Aldon
@EddieMongeJr Wherever you want to define your custom error handling, e.g., directly in the pipe. Also, have a look at the validation errors: github.com/typestack/class-validator#validation-errorsSile
Dou you have an example on how tonuse that pipe in a controller?Moua
@Moua Have a look at the binding pipes docs: docs.nestjs.com/pipes#binding-pipesSile
Did anyone tried a FluentValidation style? like: fluentvalidation-ts.alexpotter.dev/docs/overview.htmlTrochlear
What about types for conditional undefined prop? Taking your example, I'm expecting on update that the dto.name in controller is undefinedBolme
T
9

Have you tried the ValidateIf statement?

You can have multiple validations for props1 or props2 and apply them if selectedCategory is "category 1" or "category 2" accordingly.

Terribly answered 5/6, 2021 at 18:49 Comment(0)
N
0

Exactly this is the feature why I miss joi in nestjs. It would be awesome if nestjs has first class support for joi or class-validator should include something similar.

Neuroglia answered 5/5 at 18:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.