From 941b4dadc2198570cdd150eea94b1c4b16bf03f7 Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Thu, 7 Dec 2023 17:38:19 +0100 Subject: [PATCH 01/10] refactor: improve error message of isenum validator --- package-lock.json | 6 +- packages/custom-decorators/package.json | 3 +- .../src/is-enum.decorator.ts | 36 +++++++++ .../test/is-enum-decorator.test.ts | 79 +++++++++++++++++++ tsconfig.json | 3 +- 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 packages/custom-decorators/src/is-enum.decorator.ts create mode 100644 packages/custom-decorators/test/is-enum-decorator.test.ts diff --git a/package-lock.json b/package-lock.json index f9855d18..c28dea91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19092,7 +19092,8 @@ "version": "1.3.13", "license": "ISC", "dependencies": { - "jwt-decode": "^4.0.0" + "jwt-decode": "^4.0.0", + "class-validator": "^0.14.0" }, "peerDependencies": { "@nestjs/common": ">=8", @@ -19200,7 +19201,8 @@ "@algoan/nestjs-custom-decorators": { "version": "file:packages/custom-decorators", "requires": { - "jwt-decode": "^4.0.0" + "jwt-decode": "^4.0.0", + "class-validator": "^0.14.0" } }, "@algoan/nestjs-google-pubsub-client": { diff --git a/packages/custom-decorators/package.json b/packages/custom-decorators/package.json index 9308cba9..501f2ad4 100644 --- a/packages/custom-decorators/package.json +++ b/packages/custom-decorators/package.json @@ -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" }, "peerDependencies": { "@nestjs/common": ">=8", diff --git a/packages/custom-decorators/src/is-enum.decorator.ts b/packages/custom-decorators/src/is-enum.decorator.ts new file mode 100644 index 00000000..4547b3d9 --- /dev/null +++ b/packages/custom-decorators/src/is-enum.decorator.ts @@ -0,0 +1,36 @@ +import { buildMessage, isEnum, ValidateBy, ValidationOptions } from 'class-validator'; + +const IS_ENUM = 'isEnum'; + +/** + * Returns the possible values from an enum (both simple number indexed and string indexed enums). + */ +// eslint-disable-next-line +function validEnumValues(entity: any): string[] { + return ( + Object.entries(entity) + // @ts-ignore + // eslint-disable-next-line + .filter(([key, value]) => isNaN(parseInt(key))) + // @ts-ignore + .map(([key, value]) => value as string) + ); +} + +export function IsEnum(entity: object, validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: IS_ENUM, + constraints: [entity, validEnumValues(entity)], + validator: { + validate: (value, args): boolean => isEnum(value, args?.constraints[0]), + defaultMessage: buildMessage( + (eachPrefix) => + `${eachPrefix}$property has the value $value but must be one of the following values: $constraint2`, + validationOptions, + ), + }, + }, + validationOptions, + ); +} diff --git a/packages/custom-decorators/test/is-enum-decorator.test.ts b/packages/custom-decorators/test/is-enum-decorator.test.ts new file mode 100644 index 00000000..8360f9c4 --- /dev/null +++ b/packages/custom-decorators/test/is-enum-decorator.test.ts @@ -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 { + 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 { + 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; + } + + 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, + ]); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 33ba066c..7f76ae5f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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 */ }, "include": [ "packages/*/src/**/*", From ec2142bdb60ab262f10fb565faa424bec2121950 Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Fri, 8 Dec 2023 11:42:54 +0100 Subject: [PATCH 02/10] refactor: refactor the new is enum function --- .../src/is-enum.decorator.ts | 41 +++---------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/packages/custom-decorators/src/is-enum.decorator.ts b/packages/custom-decorators/src/is-enum.decorator.ts index 4547b3d9..2f02cea1 100644 --- a/packages/custom-decorators/src/is-enum.decorator.ts +++ b/packages/custom-decorators/src/is-enum.decorator.ts @@ -1,36 +1,7 @@ -import { buildMessage, isEnum, ValidateBy, ValidationOptions } from 'class-validator'; +import { IsEnum as OriginalIsEnum, ValidationOptions } from 'class-validator'; -const IS_ENUM = 'isEnum'; - -/** - * Returns the possible values from an enum (both simple number indexed and string indexed enums). - */ -// eslint-disable-next-line -function validEnumValues(entity: any): string[] { - return ( - Object.entries(entity) - // @ts-ignore - // eslint-disable-next-line - .filter(([key, value]) => isNaN(parseInt(key))) - // @ts-ignore - .map(([key, value]) => value as string) - ); -} - -export function IsEnum(entity: object, validationOptions?: ValidationOptions): PropertyDecorator { - return ValidateBy( - { - name: IS_ENUM, - constraints: [entity, validEnumValues(entity)], - validator: { - validate: (value, args): boolean => isEnum(value, args?.constraints[0]), - defaultMessage: buildMessage( - (eachPrefix) => - `${eachPrefix}$property has the value $value but must be one of the following values: $constraint2`, - validationOptions, - ), - }, - }, - validationOptions, - ); -} +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, + }); From eec295076da987c0733a37e236638b56a6b60084 Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Fri, 8 Dec 2023 16:11:51 +0100 Subject: [PATCH 03/10] refactor: create a new package for the class validators --- package-lock.json | 1 + packages/nestjs-class-validators/package.json | 14 ++++++++++++++ .../src/is-enum.decorator.ts | 0 .../test/is-enum-decorator.test.ts | 2 +- tsconfig.json | 3 +-- 5 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 packages/nestjs-class-validators/package.json rename packages/{custom-decorators => nestjs-class-validators}/src/is-enum.decorator.ts (100%) rename packages/{custom-decorators => nestjs-class-validators}/test/is-enum-decorator.test.ts (97%) diff --git a/package-lock.json b/package-lock.json index c28dea91..720dc3b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33359,3 +33359,4 @@ } } } + diff --git a/packages/nestjs-class-validators/package.json b/packages/nestjs-class-validators/package.json new file mode 100644 index 00000000..c98e7fe5 --- /dev/null +++ b/packages/nestjs-class-validators/package.json @@ -0,0 +1,14 @@ +{ + "name": "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" + } +} diff --git a/packages/custom-decorators/src/is-enum.decorator.ts b/packages/nestjs-class-validators/src/is-enum.decorator.ts similarity index 100% rename from packages/custom-decorators/src/is-enum.decorator.ts rename to packages/nestjs-class-validators/src/is-enum.decorator.ts diff --git a/packages/custom-decorators/test/is-enum-decorator.test.ts b/packages/nestjs-class-validators/test/is-enum-decorator.test.ts similarity index 97% rename from packages/custom-decorators/test/is-enum-decorator.test.ts rename to packages/nestjs-class-validators/test/is-enum-decorator.test.ts index 8360f9c4..66a15157 100644 --- a/packages/custom-decorators/test/is-enum-decorator.test.ts +++ b/packages/nestjs-class-validators/test/is-enum-decorator.test.ts @@ -53,7 +53,7 @@ describe('Tests related to the custom IsEnum Decorator', () => { class TestClass { @IsEnum(CustomEnum) - testProperty: CustomEnum; + testProperty: CustomEnum = CustomEnum.FIRST_ITEM; } it('should validate the correct values', () => { diff --git a/tsconfig.json b/tsconfig.json index 7f76ae5f..413c0a25 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,8 +25,7 @@ /* Experimental Options */ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - "strictPropertyInitialization": false, /* Disables mandatory property initialization */ + "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ }, "include": [ "packages/*/src/**/*", From 82b4c0685197fd57051314556bf6e04ff6bb1ad0 Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Fri, 8 Dec 2023 16:12:25 +0100 Subject: [PATCH 04/10] feat: re export the nestjs class validators --- packages/nestjs-class-validators/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/nestjs-class-validators/src/index.ts diff --git a/packages/nestjs-class-validators/src/index.ts b/packages/nestjs-class-validators/src/index.ts new file mode 100644 index 00000000..2ee4efa3 --- /dev/null +++ b/packages/nestjs-class-validators/src/index.ts @@ -0,0 +1,2 @@ +export { IsEnum } from './is-enum.decorator'; +export * from 'class-validator'; From 59b4482336058185fbc43e74c7442a96f89891e4 Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Fri, 8 Dec 2023 16:55:01 +0100 Subject: [PATCH 05/10] chore: change new folder name --- .../{nestjs-class-validators => class-validators}/package.json | 2 +- .../{nestjs-class-validators => class-validators}/src/index.ts | 0 .../src/is-enum.decorator.ts | 0 .../test/is-enum-decorator.test.ts | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename packages/{nestjs-class-validators => class-validators}/package.json (84%) rename packages/{nestjs-class-validators => class-validators}/src/index.ts (100%) rename packages/{nestjs-class-validators => class-validators}/src/is-enum.decorator.ts (100%) rename packages/{nestjs-class-validators => class-validators}/test/is-enum-decorator.test.ts (100%) diff --git a/packages/nestjs-class-validators/package.json b/packages/class-validators/package.json similarity index 84% rename from packages/nestjs-class-validators/package.json rename to packages/class-validators/package.json index c98e7fe5..906aef2e 100644 --- a/packages/nestjs-class-validators/package.json +++ b/packages/class-validators/package.json @@ -1,5 +1,5 @@ { - "name": "nestjs-class-validators", + "name": "@algoan/nestjs-class-validators", "version": "1.0.0", "description": "", "main": "index.js", diff --git a/packages/nestjs-class-validators/src/index.ts b/packages/class-validators/src/index.ts similarity index 100% rename from packages/nestjs-class-validators/src/index.ts rename to packages/class-validators/src/index.ts diff --git a/packages/nestjs-class-validators/src/is-enum.decorator.ts b/packages/class-validators/src/is-enum.decorator.ts similarity index 100% rename from packages/nestjs-class-validators/src/is-enum.decorator.ts rename to packages/class-validators/src/is-enum.decorator.ts diff --git a/packages/nestjs-class-validators/test/is-enum-decorator.test.ts b/packages/class-validators/test/is-enum-decorator.test.ts similarity index 100% rename from packages/nestjs-class-validators/test/is-enum-decorator.test.ts rename to packages/class-validators/test/is-enum-decorator.test.ts From 722ad71c89f80b1cd3c625f4e2f9043c25a6da63 Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Fri, 8 Dec 2023 16:56:12 +0100 Subject: [PATCH 06/10] chore: config cleanup --- package.json | 3 ++- packages/custom-decorators/package.json | 3 +-- tsconfig.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 62ff8e02..35dedf3a 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/packages/custom-decorators/package.json b/packages/custom-decorators/package.json index 501f2ad4..9308cba9 100644 --- a/packages/custom-decorators/package.json +++ b/packages/custom-decorators/package.json @@ -36,8 +36,7 @@ "url": "https://github.com/algoan/nestjs-components/issues" }, "dependencies": { - "jwt-decode": "^4.0.0", - "class-validator": "^0.14.0" + "jwt-decode": "^4.0.0" }, "peerDependencies": { "@nestjs/common": ">=8", diff --git a/tsconfig.json b/tsconfig.json index 413c0a25..33ba066c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,7 @@ /* 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. */ }, "include": [ "packages/*/src/**/*", From 5909a1d06a7282cf7a8e991eeba0244b5ea5ebc8 Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Mon, 11 Dec 2023 11:07:34 +0100 Subject: [PATCH 07/10] chore: update readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 21b3f5a6..ea0e8568 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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: From f96135826b6ebab50aa7642ca5b101b669194b4a Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Mon, 11 Dec 2023 14:13:59 +0100 Subject: [PATCH 08/10] chore: add readme file to the class validator package --- packages/class-validators/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/class-validators/README.md diff --git a/packages/class-validators/README.md b/packages/class-validators/README.md new file mode 100644 index 00000000..e40a55a4 --- /dev/null +++ b/packages/class-validators/README.md @@ -0,0 +1,21 @@ +# Nestjs-class-validators + +A set of class validators. + +## Installation + +```bash +npm install --save @algoan/nestjs-class-validators +``` + +## IsEnum + +A class validators that validates the enum type. + +### Usage: + +```ts + @IsEnum(UserType) + public userType: UserType; +``` + From ea8e090a0e0568a2d115b5b95950529ed4706f3d Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Mon, 11 Dec 2023 15:04:24 +0100 Subject: [PATCH 09/10] chore: update the class validators readme --- packages/class-validators/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/class-validators/README.md b/packages/class-validators/README.md index e40a55a4..d2d029e4 100644 --- a/packages/class-validators/README.md +++ b/packages/class-validators/README.md @@ -1,6 +1,6 @@ -# Nestjs-class-validators +# Nestjs class validators -A set of 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 @@ -10,9 +10,9 @@ npm install --save @algoan/nestjs-class-validators ## IsEnum -A class validators that validates the enum type. +A class validator that validates the enum type. -### Usage: +### Usage ```ts @IsEnum(UserType) From d084e4b6d576e997c9ec82b1217a0bc95c62d79c Mon Sep 17 00:00:00 2001 From: Salim Ben Dakhlia Date: Mon, 11 Dec 2023 15:04:43 +0100 Subject: [PATCH 10/10] chore: add jsdoc to the isenum validator --- packages/class-validators/src/is-enum.decorator.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/class-validators/src/is-enum.decorator.ts b/packages/class-validators/src/is-enum.decorator.ts index 2f02cea1..629ece3c 100644 --- a/packages/class-validators/src/is-enum.decorator.ts +++ b/packages/class-validators/src/is-enum.decorator.ts @@ -1,5 +1,11 @@ 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 => OriginalIsEnum(entity, { message: '$property has the value $value but must be one of the following values: $constraint2',