Skip to content

Commit

Permalink
refactor: improve error message of isenum validator
Browse files Browse the repository at this point in the history
  • Loading branch information
Bendakh committed Dec 7, 2023
1 parent 5e32721 commit d0d221e
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 4 deletions.
6 changes: 4 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 packages/custom-decorators/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
36 changes: 36 additions & 0 deletions packages/custom-decorators/src/is-enum.decorator.ts
Original file line number Diff line number Diff line change
@@ -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,
);
}
79 changes: 79 additions & 0 deletions packages/custom-decorators/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;
}

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,
]);
});
});
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/**/*",
Expand Down

0 comments on commit d0d221e

Please sign in to comment.