Rusty Gold
Rusty Gold

Reputation: 69

NestJs + class-validator validate either one optional parameter

I'd like to do a following body validation in my NestJs app:

a and b are optional properties if one of them is supplied, otherwise one of them should be required

payload: { a: 'some value', c: 'another value' } -> OK
payload: { b: 'some value', c: 'another value' } -> OK
payload: { a: 'some value1', b: 'some value2', c: 'another value' } -> OK
payload: { c: 'another value' } -> Error: either a or b should be present in the payload`

I have the following DTO:

class MyDto {
  @OneOfOptionalRequired(['a', 'b'])
  @ApiProperty()
  @IsString()
  @IsOptional()
  a: string

  @OneOfOptionalRequired(['a', 'b'])
  @ApiProperty()
  @IsString()
  @IsOptional()
  b: string

  @ApiProperty()
  @IsString()
  c: string
}

I've tried creating my own decorator which will perform described validation:

export function OneOfOptionalRequired(requiredFields: string[], validationOptions?: ValidationOptions) {
  return function (target: object, propertyName: string) {
    registerDecorator({
      name: 'oneOfOptionalRequired',
      target: target.constructor,
      propertyName: propertyName,
      options: {
        message: `Missing one of the required fields [${requiredFields}]`,
        ...validationOptions
      },
      validator: {
        validate(value: unknown, args: ValidationArguments) {
          const payload = args.object
          return requiredFields.some(x => has(payload, x))
        },
      },
    })
  }
}

But actually it does not work for the case when only c property is present in the payload, because @IsOptional() turns off all of the decorators. If I remove @IsOptional() for a and b then I will get the error saying that a and b should not be empty. So I'm kind of stuck here

Upvotes: 2

Views: 8531

Answers (2)

mbagsik
mbagsik

Reputation: 1

You can expand the ValidateIf decorator to add when those two fields exists.

@ValidateIf(dto => !dto.b || (dto.a && dto.b))
@IsString()
a?: string;

@ValidateIf(dto => !dto.a || (dto.a && dto.b))
@IsString()
b?: string;

Upvotes: 0

Agus Neira
Agus Neira

Reputation: 505

You could make your own decorator, especially if this is a repeating pattern in your application or if you expect to need this replicated over more than two properties in the same DTO. However, for this specific case, you could solve it in a more straight-forward manner:

@ValidateIf(dto => typeof dto.b === 'undefined')
@IsString()
a?: string;

@ValidateIf(dto => typeof dto.a === 'undefined')
@IsString()
b?: string;

Of course, you can export and re-use this as following:

export const RequiredIfPropertyMissing = (property: string) => 
  ValidateIf(dto: any => typeof dto[property] === 'undefined');

Hope it helps, and good luck!

Upvotes: 2

Related Questions