typestack / class-validator

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

fix: custom decorators ignore `stopAtFirstError` option #2234

Open EizFeren opened 1 year ago

EizFeren commented 1 year ago

Description

I've got a strange bug, when my custom decorators are stopping each other even with stopAtFirstError option set to false. Perharps standard class validator default decorators working as expected.

Minimal code-snippet showcasing the problem

class SomeDto {
  @IsStrongPassword({
    minNumbers: 1,
    minLowercase: 0,
    minUppercase: 0,
    minSymbols: 0,
  }, {
    message: 'VALIDATION_PASSWORD_STRENTGH',
  })
  @MaxLength(20, {
    message: 'VALIDATION_PASSWORD_LENGTH_MAX',
  })
  @MinLength(8, {
    message: 'VALIDATION_PASSWORD_LENGTH_MIN',
  })
  @Requires('currentPassword', { // custom decorator, checks `currentPassword` field is defined
    message: 'VALIDATION_CURRENT_PASSWORD_MISSING',
  })
  @Requires('newPasswordRepeat', { // custom decorator, checks `newPasswordRepeat` field is defined
    message: 'VALIDATION_PASSWORD_REPEAT_MISSING',
  })
  @IsOptional()
  newPassword?: string;
}

Input, provided for this dto:

{
  "newPassword": "qwe"
}

Custom decorator definition:

import {
  registerDecorator,
  ValidationArguments,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from 'class-validator';

export function Requires(property: string, validationOptions?: ValidationOptions) {
  return (object: any, propertyName: string) => {
    registerDecorator({
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [
        property,
      ],
      validator: RequiresConstraint,
    });
  };
}

@ValidatorConstraint({
  name: 'Requires',
})
class RequiresConstraint implements ValidatorConstraintInterface {
  validate(value: any, args: ValidationArguments) {
    const [ relatedPropertyName ] = args.constraints;
    const relatedValue = (args.object as any)[relatedPropertyName];

    return relatedValue !== undefined;
  }
}

Expected behavior

I'm expecting all custom decorator checks to be done, as below:

{
  "messages": [
    "VALIDATION_CURRENT_PASSWORD_MISSING",
    "VALIDATION_PASSWORD_REPEAT_MISSING",
    "VALIDATION_PASSWORD_LENGTH_MIN",
    "VALIDATION_PASSWORD_STRENGTH"
  ]
}

Actual behavior

Check for newPasswordRepeat field was skipped

{
  "messages": [
    "VALIDATION_CURRENT_PASSWORD_MISSING",
    "VALIDATION_PASSWORD_LENGTH_MIN",
    "VALIDATION_PASSWORD_STRENGTH"
  ]
}
EizFeren commented 1 year ago

I've tried also with class validator Validate decorator and custom constraint (RequiresConstraint in example above), same result