Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add constraints metadata to ValidationError #1758

Closed
buddh4 opened this issue Oct 8, 2022 · 6 comments
Closed

feature: Add constraints metadata to ValidationError #1758

buddh4 opened this issue Oct 8, 2022 · 6 comments
Labels
flag: needs discussion Issues which needs discussion before implementation. type: feature Issues related to new features.

Comments

@buddh4
Copy link

buddh4 commented Oct 8, 2022

Description

Currently there seems to be no easy way for custom translations of error messages with constraints as described in #169

The ValidationError interface already contains enough information to create custom translation keys such as validation.min but is missing the actual constraint metadata which is required in order to fully translate or customize a validation error message.

To get the additional metadata we currently need to either use the context option in all of our decorators and redundantly pass the constraints as context, or wrap all available decorators in custom decorators and automatically pass the constraints as context as described in #169 (comment)

If we could access the constraint metadata in the ValidationError by default, it would heavily simplify this use-case as in the following example.

Example

Lets assume this is my translation file:

{
  "validation.max": "{property} must be less or equal to {constraint1}"
}

My custom translation function looks something like this:

interface ErrorMetadata {
  property: string;
  type: string;
  message: string;
  metadata: { constraints: any[] }
  context: any;
}

const translateError = (error: ErrorMetadata) => {
  const translationKey = `validation.${error.type}`;
  const translatedProperty = translate(`myform.properties.${error.property}`);

// instead of hardcoded translation options
// we could auto generate translation options form property and constraints
  return translate(translationKey, { 
    property: translatedProperty,
    constraint1: error.metadata.constraints[0] 
  });
}

const getFirstError(error: ValidationError): ErrorMetadata {
   // Determine the first error and map to ErrorMetadata format
}


const validationErrors = validate(loginModel);
// Get first error and prepare it to be usable in translate function above
const firstErrors = validationErrors.map(getFirstError);
const errors = firstErrors.map((error) => { property: error.property, message: translateError(error) } )

Proposed solution

The following branch includes such a change develop...buddh4:class-validator:feature/include-validation-metadata-to-validationerror

Maybe there should be a validation and/or global setting to opt-in to this behavior in order to not change the current ValidationError output.

If this feature and solution is of interest, I could keep working on this change.

Another solution

Another solution for this problem would simply adding an additional ValidatorOptions option e.g. transformMessage which is used to generate the message e.g. here https://github.com/typestack/class-validator/blob/develop/src/validation/ValidationExecutor.ts#L407

@buddh4 buddh4 added flag: needs discussion Issues which needs discussion before implementation. type: feature Issues related to new features. labels Oct 8, 2022
@ghost
Copy link

ghost commented Oct 18, 2022

I have the same problem, for the same reasons. I also need the constraints values to translate validation errors

@buddh4
Copy link
Author

buddh4 commented Oct 24, 2022

@yachtmee I've seen that you've forked my proposed feature branch, please note that this was just a quick prototype. I think one test was failing, I did not have the time or intent to further work on this unless there is some interest from the maintainers.

@ghost
Copy link

ghost commented Oct 25, 2022

I think we can solve our problem without making any changes to the class by using the context option and passing in the min max and any values you need to retrieve after validation

https://github.com/typestack/class-validator#passing-context-to-decorators

@buddh4
Copy link
Author

buddh4 commented Oct 25, 2022

Yes, unfortunately I have a ton of models and my intend was to push a build-in solution which would have the following advantages:

1. No need for a custom solution

With the context solution we have to define a custom context format and use it on every decorator otherwise it breaks the translation.

2. No boiler plate code (error prone)

The requirement to add a context with custom format to every decorator with redundant validation rules is not only ugly but also more error prone.

3. Third party models

Not sure if this use-case is very common, but if you have models from external libraries the solution won't work.

4. Third party translation library

A build-in solution would ease building a translation library on top of class-validator without requiring any extra work on the consumer side. This way the class-validator library itself would not need to care about internationalization, which for whatever reason does not seem to have any priority at the moment.

I just think such a change would heavily benefit this library without introducing any complexity or compatibility issues (when choosing opt-in or the second purposed solution). Internationalization is a requirement of most applications and websites and I think this issue should definitely have a higher priority.

@buddh4
Copy link
Author

buddh4 commented Oct 25, 2022

Not sure why I did not think of this earlier, but we can access the constraints as follows, we just need our model object:

import { getMetadataStorage } from 'class-validator';

const validationMetas = getMetadataStorage().getTargetValidationMetadatas(
  model.constructor,
  model.constructor.name,
  true,
  false,
);

const constraint = validationMetas.find(meta => meta.propertyName === 'someProperty')?.constraints;

For me this is sufficient and can also be used for example when validating a model in the backend and translating the errors in the frontend, you just have to add the constraints in the response.

When setting options.validationError = { target: true, value: true }; the validationError also contains the model and value which we can use to get the metadata.

@buddh4 buddh4 closed this as completed Oct 25, 2022
@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
flag: needs discussion Issues which needs discussion before implementation. type: feature Issues related to new features.
Development

No branches or pull requests

1 participant