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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ A collection of [NestJS](https://docs.nestjs.com) components. This repository is
- [NestJS Google Cloud PubSub MicroService](#nestjs-google-cloud-pubsub-microservice)
- [NestJS Google Cloud PubSub Client Proxy](#nestjs-google-cloud-pubsub-client-proxy)
- [NestJS custom decorators](#nestjs-custom-decorators)
- [NestJS class validators](#nestjs-class-validators)

## NestJS Pagination

Expand Down Expand Up @@ -57,6 +58,12 @@ A set of custom decorators for NestJS.

See [the documentation here](./packages/custom-decorators).

## NestJS class validators

A package containing overriden class validators.

See [the documentation here](./packages/class-validators).

# Contribution

This repository is managed by [Lerna.js](https://lerna.js.org). If you want to contribute, you need to follow these instructions:
Expand Down
7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"@algoan/nestjs-google-pubsub-microservice": "file:packages/google-pubsub-microservice",
"@algoan/nestjs-http-exception-filter": "file:packages/http-exception-filter",
"@algoan/nestjs-logging-interceptor": "file:packages/logging-interceptor",
"@algoan/nestjs-pagination": "file:packages/pagination"
"@algoan/nestjs-pagination": "file:packages/pagination",
"@algoan/nestjs-class-validators": "file:packages/class-validators"
}
}
21 changes: 21 additions & 0 deletions packages/class-validators/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Nestjs class validators

A set of class validators based on the class-validator package (https://www.npmjs.com/package/@nestjs/class-validator/v/0.13.1).

## Installation

```bash
npm install --save @algoan/nestjs-class-validators
```

## IsEnum

A class validator that validates the enum type.

### Usage

```ts
@IsEnum(UserType)
public userType: UserType;
```

14 changes: 14 additions & 0 deletions packages/class-validators/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@algoan/nestjs-class-validators",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"class-validator": "^0.14.0"
}
}
2 changes: 2 additions & 0 deletions packages/class-validators/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { IsEnum } from './is-enum.decorator';
export * from 'class-validator';
13 changes: 13 additions & 0 deletions packages/class-validators/src/is-enum.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IsEnum as OriginalIsEnum, ValidationOptions } from 'class-validator';

/**
* Checks if a given value is the member of the provided enum.
*
* This an override for the class-validator IsEnum validator.
* The error message is enhanced with the invalid value
*/
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.

OriginalIsEnum(entity, {
message: '$property has the value $value but must be one of the following values: $constraint2',
...validationOptions,
});
79 changes: 79 additions & 0 deletions packages/class-validators/test/is-enum-decorator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { IsEnum } from '../src/is-enum.decorator';
import { Validator, ValidatorOptions } from 'class-validator';

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);
}

describe('Tests related to the custom IsEnum Decorator', () => {
enum CustomEnum {
FIRST_ITEM = 'first-item',
SECOND_ITEM = 'second-item',
}

class TestClass {
@IsEnum(CustomEnum)
testProperty: CustomEnum = CustomEnum.FIRST_ITEM;
}

it('should validate the correct values', () => {
return validateValues(new TestClass(), [
CustomEnum.FIRST_ITEM,
CustomEnum.SECOND_ITEM,
'first-item',
'second-item',
]);
});

it('should not validate invalid values and return the correct errors', () => {
const validationType = 'isEnum';
const firstMessage =
'testProperty has the value false-value-1 but must be one of the following values: first-item, second-item';
const secondMessage =
'testProperty has the value false-value-2 but must be one of the following values: first-item, second-item';
return checkReturnedError(new TestClass(), ['false-value-1', 'false-value-2'], validationType, [
firstMessage,
secondMessage,
]);
});
});
Loading