AlexJPotter / fluentvalidation-ts

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

Support for error codes #7

Closed tnotheis closed 4 years ago

tnotheis commented 4 years ago

Are there any plans to support error codes as part of the validation result? I imagine something like this:

this.ruleFor('name')
  .notEmpty()
  .withMessage('Please enter your name')
  .withCode('invalidName');

The problem is that the ValueValidationResult would have to be changed to contain objects instead of simple strings:

{ 
    name: {
        message: 'Please enter your name' 
        code: 'invalidName'
    }
}

which would probably be a breaking change.

AlexJPotter commented 4 years ago

Hi, thanks for getting in touch!

I've not got any plans for something like this at the moment - as you've pointed out it would change the typing of the validation errors object, which in particular would impact the compatibility with Formik.

Depending on exactly how you're using the library at the moment one potential workaround would be to set the message to a JSON string representing an object with both a message and a code:

this.ruleFor('name')
  .notEmpty()
  .withMessage(JSON.stringify({ message: 'Please enter your name', code: 'invalidName' })); 

You could then decode the result to get the object you're after:

const result = validator.validate({ name: '' });

const nameResult = JSON.parse(result['name']);

nameResult.message; // 'Please enter your name'
nameResult.code; // 'invalidName'

This should work but is obviously a bit hacky. If you need a more bespoke solution you'll probably need to fork the repo and build the functionality in properly.

I'm going to close this issue for now, but if more people start asking for this I'll revisit it. Thanks again!

ramunsk commented 3 years ago

This might work

type ValidationMessages<T> = { [key in keyof T]: { errorCode: string; message: string } };

export abstract class BaseValidator<T> extends Validator<T> {
    getValidationMessages(value: T): ValidationMessages<T> {
        const messages = super.validate(value);

        const validationMessages = Object.entries(messages).reduce((acc, [prop, message]:[string, string]) => {
            const [errorCode, msg] = message?.indexOf('|') > -1 ? message.split('|') : [undefined, message];
            acc[prop] = { errorCode, message: msg };
            return acc;
        }, {} as ValidationMessages<T>);

        return validationMessages;
    }
}

Extend BaseValidator, use pipe (|) to separate error code from message in withMessage and call getErrorMessages(value)

interface Foo {
    bar: string;
}

class FooValidator extends BaseValidator<Foo> {
    constructor() {
        super();

        this.ruleFor('bar').notNull().notEmpty().withMessage('error1|`bar` cannot be empty');
    }
}

new FooValidator().getValidationMessages({bar: undefined});
// => { bar: { errorCode: 'error1', message: '`bar` cannot be empty'}}

Code not tested and probably could be more elegant.