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

[FEAT] Improve error message of isEnum validator #830

Merged
merged 10 commits into from
Dec 11, 2023

Conversation

Bendakh
Copy link
Contributor

@Bendakh Bendakh commented Dec 7, 2023

Description

This PR aims to improve the error message of the IsEnum validator. The new error will display the invalid value of the property that was not validated.

Motivation and Context

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

@g-ongenae g-ongenae added the enhancement New feature or request label Dec 8, 2023
@g-ongenae g-ongenae changed the title [ALG-1758] Improve error message of isEnum validator [FEAT] Improve error message of isEnum validator Dec 8, 2023
Comment on lines 4 to 8
function validateValues(
object: { testProperty: any },
values: any[],
validatorOptions?: ValidatorOptions,
): Promise<any> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid to use any. If there is no way to type the function properly (using an interface or generics), it is better to use unknown. Here, actually, it is not big deal because it is a test implementation, but it is better for readability and test maintenance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I used the any because I got an error with Object.entries saying that it can't accept arguments of type unkown

Comment on lines 4 to 46
function validateValues(
object: { testProperty: any },
values: any[],
validatorOptions?: ValidatorOptions,
): Promise<any> {
const validator = new Validator();
const promises = values.map((value) => {
object.testProperty = value;
return validator.validate(object, validatorOptions).then((errors) => {
expect(errors.length).toEqual(0);
if (errors.length > 0) {
console.log(`Unexpected errors: ${JSON.stringify(errors)}`);
throw new Error('Unexpected validation errors');
}
});
});

return Promise.all(promises);
}

function checkReturnedError(
object: { testProperty: any },
values: any[],
validationType: string,
messages: string[],
validatorOptions?: ValidatorOptions,
): Promise<any> {
let messagesIncrementor: number = 0;
const validator = new Validator();
const promises = values.map((value) => {
object.testProperty = value;
return validator.validate(object, validatorOptions).then((errors) => {
expect(errors.length).toEqual(1);
expect(errors[0].target).toEqual(object);
expect(errors[0].property).toEqual('testProperty');
expect(errors[0].constraints).toEqual({ [validationType]: messages[messagesIncrementor] });
expect(errors[0].value).toEqual(value);
messagesIncrementor++;
});
});

return Promise.all(promises);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, those utility function adds more complexity than they solve. And both are used once, so they didn't reduce duplication.

Do you think they are useful? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though that maybe we can use them for later and keep the tests simple with the minimum number of code lines

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I noticed that it is possible to use the decorator as a normal function (without annotation @). As a result, there is no need to duplicate the implementation of the original decorator. Instead, you can write:

import { IsEnum as OriginalIsEnum, ValidationOptions } from 'class-validator';

export const IsEnum = (entity: object, validationOptions?: ValidationOptions): PropertyDecorator => OriginalIsEnum(entity, { message: '$property has the value $value but must be one of the following values: $constraint2', ...validationOptions });

tsconfig.json Outdated
@@ -25,7 +25,8 @@

/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"strictPropertyInitialization": false, /* Disables mandatory property initialization */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you change this config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for the testing classes. The initialization of the properties is mandatory so with this config we can avoid the error that we are getting if we don't initialize them

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it is a good idea to "relax" a TypeScript option just for a test implementation. You cannot initialise them instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do that instead then

@@ -36,7 +36,8 @@
"url": "https://github.com/algoan/nestjs-components/issues"
},
"dependencies": {
"jwt-decode": "^4.0.0"
"jwt-decode": "^4.0.0",
"class-validator": "^0.14.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be a peer dependency instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next task is to re export all the validators from class-validator so that we import validators through a single package (i.e. @algoan/custom-decorators). With this coming feature, I think it makes sense to have it as direct dependency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I didn't know that. Then, I think it would be better to create a new package @algoan/nestjs-class-validator instead of updating @algoan/nestjs-custom-decorators.

The current decorators in this package are dedicated to parameters instead of validation and are few in numbers. Adding the whole class-validator will create some confusion, I fear. Hence, a new package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should not be changed by this PR anymore.

tsconfig.json Outdated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

packages/nestjs-class-validators/src/index.ts Outdated Show resolved Hide resolved
@@ -0,0 +1,14 @@
{
"name": "nestjs-class-validators",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"name": "nestjs-class-validators",
"name": "@algoan/nestjs-class-validators",

It also should be added it here:

"dependencies": {
"@algoan/nestjs-custom-decorators": "file:packages/custom-decorators",

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The folder can be named class-validators directly for coherency with the other packages. (I.e. nestjs in the package name, not in the folder name.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading your comment, I realised that the validators are not really linked to nestjs 😅 So I don't know if it is okay?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be put in our mono repo instead 🤪

Comment on lines 12 to 18
return validator.validate(object, validatorOptions).then((errors) => {
expect(errors.length).toEqual(0);
if (errors.length > 0) {
console.log(`Unexpected errors: ${JSON.stringify(errors)}`);
throw new Error('Unexpected validation errors');
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why expecting errors.length to be 0 then if errors.length > 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the expect in this case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in a test there must be expecting something. In your test, you just call validateValues().
I think it'd be cleaner validateValues() just return a list of validation errors. And in the test you expect the list after the function call.

Copy link
Contributor Author

@Bendakh Bendakh Dec 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. I didn't change much of the implementation for the tests to as I said in another comment avoid breaking anything.

): Promise<any> {
const validator = new Validator();
const promises = values.map((value) => {
object.testProperty = value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is just one instance of object but you set and test many async validation parallelly on the same property, I'm not sure it works correctly as expected

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the test code used in the class-validator repository. I didn't want to change it so that I don't break anything


export const IsEnum = (entity: object, validationOptions?: ValidationOptions): PropertyDecorator =>
OriginalIsEnum(entity, {
message: '$property has the value $value but must be one of the following values: $constraint2',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$constraint1 refers to entity, so $constraint2 refers to what in this case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To the validEnumEntities, see code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks. I don't see they indicate in the offical doc page

@@ -0,0 +1,7 @@
import { IsEnum as OriginalIsEnum, ValidationOptions } from 'class-validator';

export const IsEnum = (entity: object, validationOptions?: ValidationOptions): PropertyDecorator =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this function is part of the public API, you should probably add JSDoc.

Copy link
Contributor

@amarlankri amarlankri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this package is public, we need to be stricter on the documentation.

@@ -0,0 +1,21 @@
# Nestjs-class-validators
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Nestjs-class-validators
# Nestjs class validators


## IsEnum

A class validators that validates the enum type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A class validators that validates the enum type.
A class validator that validates the enum type.


A class validators that validates the enum type.

### Usage:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Usage:
### Usage

@@ -0,0 +1,21 @@
# Nestjs-class-validators

A set of class validators.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the description, I think it is important to say that it is based on the existing class-validators (with a link) since most of the validators are just re-exported.

Copy link
Contributor

@amarlankri amarlankri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the adjustments!

@g-ongenae g-ongenae merged commit 787895d into algoan:master Dec 11, 2023
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants