Skip to content

Commit

Permalink
feat: return possible values in error message for @IsEnum decorator (
Browse files Browse the repository at this point in the history
  • Loading branch information
NoNameProvided authored Dec 3, 2022
1 parent f0541a6 commit 1f4a89c
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 23 deletions.
17 changes: 13 additions & 4 deletions src/decorator/typechecker/IsEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,34 @@ import { buildMessage, ValidateBy } from '../common/ValidateBy';
export const IS_ENUM = 'isEnum';

/**
* Checks if a given value is an enum
* Checks if a given value is the member of the provided enum.
*/
export function isEnum(value: unknown, entity: any): boolean {
const enumValues = Object.keys(entity).map(k => entity[k]);
return enumValues.includes(value);
}

/**
* Checks if a given value is an enum
* Returns the possible values from an enum (both simple number indexed and string indexed enums).
*/
function validEnumValues(entity: any): string[] {
return Object.entries(entity)
.filter(([key, value]) => isNaN(parseInt(key)))
.map(([key, value]) => value as string);
}

/**
* Checks if a given value is the member of the provided enum.
*/
export function IsEnum(entity: object, validationOptions?: ValidationOptions): PropertyDecorator {
return ValidateBy(
{
name: IS_ENUM,
constraints: [entity],
constraints: [entity, validEnumValues(entity)],
validator: {
validate: (value, args): boolean => isEnum(value, args?.constraints[0]),
defaultMessage: buildMessage(
eachPrefix => eachPrefix + '$property must be a valid enum value',
eachPrefix => eachPrefix + '$property must be one of the following values: $constraint2',
validationOptions
),
},
Expand Down
54 changes: 35 additions & 19 deletions test/functional/validation-functions-and-decorators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,12 @@ describe('IsArray', () => {
});

describe('IsEnum', () => {
enum MyEnum {
enum MyDefaultIndexedEnum {
First,
Second,
}

enum MyCustomIndexedEnum {
First = 1,
Second = 999,
}
Expand All @@ -894,62 +899,73 @@ describe('IsEnum', () => {
Second = 'second',
}

const validValues = [MyEnum.First, MyEnum.Second];
const validValues = [MyCustomIndexedEnum.First, MyCustomIndexedEnum.Second];
const validStringValues = [MyStringEnum.First, MyStringEnum.Second];
const invalidValues = [true, false, 0, {}, null, undefined, 'F2irst'];
const invalidValues = [true, false, 42, {}, null, undefined, 'F2irst'];

class MyClass {
@IsEnum(MyEnum)
someProperty: MyEnum;
class MyClassOne {
@IsEnum(MyDefaultIndexedEnum)
someProperty: MyDefaultIndexedEnum;
}

class MyClass2 {
class MyClassTwo {
@IsEnum(MyCustomIndexedEnum)
someProperty: MyCustomIndexedEnum;
}

class MyClassThree {
@IsEnum(MyStringEnum)
someProperty: MyStringEnum;
}

it('should not fail if validator.validate said that its valid', () => {
return checkValidValues(new MyClass(), validValues);
return checkValidValues(new MyClassTwo(), validValues);
});

it('should not fail if validator.validate said that its valid (string enum)', () => {
return checkValidValues(new MyClass2(), validStringValues);
return checkValidValues(new MyClassThree(), validStringValues);
});

it('should fail if validator.validate said that its invalid', () => {
return checkInvalidValues(new MyClass(), invalidValues);
return checkInvalidValues(new MyClassTwo(), invalidValues);
});

it('should fail if validator.validate said that its invalid (string enum)', () => {
return checkInvalidValues(new MyClass2(), invalidValues);
return checkInvalidValues(new MyClassThree(), invalidValues);
});

it('should not fail if method in validator said that its valid', () => {
validValues.forEach(value => expect(isEnum(value, MyEnum)).toBeTruthy());
validValues.forEach(value => expect(isEnum(value, MyCustomIndexedEnum)).toBeTruthy());
});

it('should not fail if method in validator said that its valid (string enum)', () => {
validStringValues.forEach(value => expect(isEnum(value, MyStringEnum)).toBeTruthy());
});

it('should fail if method in validator said that its invalid', () => {
invalidValues.forEach(value => expect(isEnum(value, MyEnum)).toBeFalsy());
invalidValues.forEach(value => expect(isEnum(value, MyCustomIndexedEnum)).toBeFalsy());
});

it('should fail if method in validator said that its invalid (string enum)', () => {
invalidValues.forEach(value => expect(isEnum(value, MyStringEnum)).toBeFalsy());
});

it('should return error object with proper data', () => {
it('should return error with proper message for default indexed enum', () => {
const validationType = 'isEnum';
const message = 'someProperty must be a valid enum value';
return checkReturnedError(new MyClass(), invalidValues, validationType, message);
const message = 'someProperty must be one of the following values: 0, 1';
return checkReturnedError(new MyClassOne(), invalidValues, validationType, message);
});

it('should return error with proper message for custom indexed enum', () => {
const validationType = 'isEnum';
const message = 'someProperty must be one of the following values: 1, 999';
return checkReturnedError(new MyClassTwo(), invalidValues, validationType, message);
});

it('should return error object with proper data (string enum)', () => {
it('should return error with proper message for string enum', () => {
const validationType = 'isEnum';
const message = 'someProperty must be a valid enum value';
checkReturnedError(new MyClass2(), invalidValues, validationType, message);
const message = 'someProperty must be one of the following values: first, second';
return checkReturnedError(new MyClassThree(), invalidValues, validationType, message);
});
});

Expand Down

0 comments on commit 1f4a89c

Please sign in to comment.