AlexJPotter / fluentvalidation-ts

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

ValidationErrors returns empty object if no errors #25

Closed tpetersons closed 2 years ago

tpetersons commented 2 years ago

Hello,

First of all, thanks for the library! It's awesome as I have been using fluent validator for C# for a long time, and was missing this in typescript. Great work.

My issue - If there are no validation errors, methods .validate and .validateAsync returns an empty object {}. Then to check if there are errors I have to each time check if return value is an empty object.

Example (using lodash):

const validationError = await Validators.CreateOrderRequestValidator(req).validateAsync(req.body);
_.isEmpty(validationError) ? res.status(200).json(await addOrder(req.body)) : res.status(400).json(validationError);

Example (without lodash):

const validationError = await Validators.CreateOrderRequestValidator(req).validateAsync(req.body);
Object.keys(validationError).length > 0 ? res.status(200).json(await addOrder(req.body)) : res.status(400).json(validationError);

For me it's a bit inconvenient, would prefer if these methods would either return ValidationError or null instead {} ? So I could replace above code with:

const validationError = await Validators.CreateOrderRequestValidator(req).validateAsync(req.body);
validationError  ? res.status(400).json(validationError) : res.status(200).json(await addOrder(req.body));

Perhaps as a workaround I could try wrapping these methods in my own custom one, and check this empty object once.. Thanks!

AlexJPotter commented 2 years ago

Hi there! 👋

Thanks for the kind words - I'm glad the library has been useful to you 😃

In terms of your question - the design of fluentvalidation-ts has been quite heavily influenced/motivated by the Formik forms library for React. I wanted to make it as simple as possible to use fluentvalidation-ts with Formik - in particular I wanted it so that you could just do <Formik validate={myValidator.validate} ... /> and it would "just work".

Looking at the Formik API for the validate prop, we can see it's expecting an object, which is why fluentvalidation-ts returns an empty object, rather than null:

Validation function. Must return an error object (or promise that throws an error object) where the object keys map to corresponding values.

As you've suggested, I'd probably pull out a helper method or two to make your life easier. I'd maybe define a transformation function that can turn out-of-the-box validators into ones that have a validate function which behaves as you want. Perhaps something like this (I've used a sync example rather than async, but you get the idea):

const isEmptyObject = (value: object): boolean => Object.keys(value).length === 0;

const getCustomValidator = <TModel extends object>(validator: Validator<TModel>) => ({
  validate: (model: TModel): ValidationErrors<TModel> | null => {
    const validationErrors = validator.validate(model);
    return isEmptyObject(validationErrors) ? null : validationErrors;
  },
});

const myValidator = getCustomValidator(new MyValidator());

const validationResult = myValidator.validate({ /* ... */ });
// Will be `null` if no errors

I hope this helps!

tpetersons commented 2 years ago

Makes sense. Thanks @AlexJPotter !