-
Notifications
You must be signed in to change notification settings - Fork 40
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
Conversation
01bf2db
to
d0d221e
Compare
function validateValues( | ||
object: { testProperty: any }, | ||
values: any[], | ||
validatorOptions?: ValidatorOptions, | ||
): Promise<any> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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); | ||
} |
There was a problem hiding this comment.
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? 🤔
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 */ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same.
@@ -0,0 +1,14 @@ | |||
{ | |||
"name": "nestjs-class-validators", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"name": "nestjs-class-validators", | |
"name": "@algoan/nestjs-class-validators", |
It also should be added it here:
nestjs-components/package.json
Lines 91 to 92 in 5e32721
"dependencies": { | |
"@algoan/nestjs-custom-decorators": "file:packages/custom-decorators", |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 🤪
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'); | ||
} | ||
}); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
5d041a9
to
5909a1d
Compare
@@ -0,0 +1,7 @@ | |||
import { IsEnum as OriginalIsEnum, ValidationOptions } from 'class-validator'; | |||
|
|||
export const IsEnum = (entity: object, validationOptions?: ValidationOptions): PropertyDecorator => |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
packages/class-validators/README.md
Outdated
@@ -0,0 +1,21 @@ | |||
# Nestjs-class-validators |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Nestjs-class-validators | |
# Nestjs class validators |
packages/class-validators/README.md
Outdated
|
||
## IsEnum | ||
|
||
A class validators that validates the enum type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A class validators that validates the enum type. | |
A class validator that validates the enum type. |
packages/class-validators/README.md
Outdated
|
||
A class validators that validates the enum type. | ||
|
||
### Usage: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
### Usage: | |
### Usage |
packages/class-validators/README.md
Outdated
@@ -0,0 +1,21 @@ | |||
# Nestjs-class-validators | |||
|
|||
A set of class validators. |
There was a problem hiding this comment.
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.
There was a problem hiding this 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!
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