AlexJPotter / fluentvalidation-ts

A TypeScript-first library for building strongly-typed validation rules
Apache License 2.0
87 stars 6 forks source link

multiple when/unless calls in the same chain not consistent with FluentValidation behaviour #45

Open maxwell-01 opened 1 year ago

maxwell-01 commented 1 year ago

In FluentValidation it is possible to string together multiple when statements for a single property. In this library the behaviour does not work as expected. For example:

this.ruleFor("days")
      .notEmpty()
      .must([beNumeric, beAnInteger, beGreaterThanOrEqualTo(0)])
      .withMessage("days must be greater than or equal to 0")
      .must([beNumeric, beAnInteger, beLessThan(2000000000)])
      .withMessage("The maximum allowed number size is 1,999,999,999")
      .must((value, model) => asNumber(value) > 0 || asNumber(model.otherDays) > 0)
      .withMessage("days and otherDays cannot both be 0")
      .when((model) => model.dayCode== "weekday")
      .null()
      .withMessage("days must not be entered for weekendDays")
      .when((model) => model.dayCode!= "weekendDay");

Is not the same as:

this.ruleFor("days")
      .notEmpty()
      .must([beNumeric, beAnInteger, beGreaterThanOrEqualTo(0)])
      .withMessage("days must be greater than or equal to 0")
      .must([beNumeric, beAnInteger, beLessThan(2000000000)])
      .withMessage("The maximum allowed number size is 1,999,999,999")
      .must((value, model) => asNumber(value) > 0 || asNumber(model.otherDays) > 0)
      .withMessage("days and otherDays cannot both be 0")
      .when((model) => model.dayCode== "weekday");

this.ruleFor("days")
      .null()
      .withMessage("days must not be entered for weekendDays")
      .when((model) => model.dayCode!= "weekendDay");

The second set of code provides the expected behaviour.

danrayson commented 2 days ago

I have a similar issue, though it doesn't fill up the validation results properly in either case.

import { Validator } from 'fluentvalidation-ts';

export type ChangePasswordRequest = {
    Password: string;
    PasswordCheck: string;
}

export class ChangePasswordRequestValidator extends Validator<ChangePasswordRequest> {

    constructor() {
        super();

        this.ruleFor('Password')
            .notEmpty().withMessage("Password is required")
            .length(8, 100).withMessage("Password must be at least 8 characters long and less than 100 chars long")
            .matches(RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^a-zA-Z\\d]).+$"))
            .withMessage("Must contain at least one lowercase letter, one uppercase letter, one number, and one special character");

        this.ruleFor('PasswordCheck')
            .notEmpty().withMessage("Confirm password is required")
            .must((x, m) => x == m.Password).withMessage("Passwords do not match");
    }
}

When I call it with 123 for both passwords, I expect that the result would be similar to;

["Password", ["Password must be at least 8 characters long and less than 100 chars long", "Must contain at least one lowercase letter, one uppercase letter, one number, and one special character"]]

But I don't, I only get

["Password", ["Password must be at least 8 characters long and less than 100 chars long"]]

I've tried splitting up the ruleFor's, but this time I see only the last check, so I get only:

["Password", ["Must contain at least one lowercase letter, one uppercase letter, one number, and one special character"]]

To clarify, in DotNet's FluentValidation's implementation you get all messages for all rules that failed in the response.