Skip to content

Commit 1f4a89c

Browse files
feat: return possible values in error message for @IsEnum decorator (#1826)
1 parent f0541a6 commit 1f4a89c

File tree

2 files changed

+48
-23
lines changed

2 files changed

+48
-23
lines changed

src/decorator/typechecker/IsEnum.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,34 @@ import { buildMessage, ValidateBy } from '../common/ValidateBy';
44
export const IS_ENUM = 'isEnum';
55

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

1414
/**
15-
* Checks if a given value is an enum
15+
* Returns the possible values from an enum (both simple number indexed and string indexed enums).
16+
*/
17+
function validEnumValues(entity: any): string[] {
18+
return Object.entries(entity)
19+
.filter(([key, value]) => isNaN(parseInt(key)))
20+
.map(([key, value]) => value as string);
21+
}
22+
23+
/**
24+
* Checks if a given value is the member of the provided enum.
1625
*/
1726
export function IsEnum(entity: object, validationOptions?: ValidationOptions): PropertyDecorator {
1827
return ValidateBy(
1928
{
2029
name: IS_ENUM,
21-
constraints: [entity],
30+
constraints: [entity, validEnumValues(entity)],
2231
validator: {
2332
validate: (value, args): boolean => isEnum(value, args?.constraints[0]),
2433
defaultMessage: buildMessage(
25-
eachPrefix => eachPrefix + '$property must be a valid enum value',
34+
eachPrefix => eachPrefix + '$property must be one of the following values: $constraint2',
2635
validationOptions
2736
),
2837
},

test/functional/validation-functions-and-decorators.spec.ts

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,12 @@ describe('IsArray', () => {
884884
});
885885

886886
describe('IsEnum', () => {
887-
enum MyEnum {
887+
enum MyDefaultIndexedEnum {
888+
First,
889+
Second,
890+
}
891+
892+
enum MyCustomIndexedEnum {
888893
First = 1,
889894
Second = 999,
890895
}
@@ -894,62 +899,73 @@ describe('IsEnum', () => {
894899
Second = 'second',
895900
}
896901

897-
const validValues = [MyEnum.First, MyEnum.Second];
902+
const validValues = [MyCustomIndexedEnum.First, MyCustomIndexedEnum.Second];
898903
const validStringValues = [MyStringEnum.First, MyStringEnum.Second];
899-
const invalidValues = [true, false, 0, {}, null, undefined, 'F2irst'];
904+
const invalidValues = [true, false, 42, {}, null, undefined, 'F2irst'];
900905

901-
class MyClass {
902-
@IsEnum(MyEnum)
903-
someProperty: MyEnum;
906+
class MyClassOne {
907+
@IsEnum(MyDefaultIndexedEnum)
908+
someProperty: MyDefaultIndexedEnum;
904909
}
905910

906-
class MyClass2 {
911+
class MyClassTwo {
912+
@IsEnum(MyCustomIndexedEnum)
913+
someProperty: MyCustomIndexedEnum;
914+
}
915+
916+
class MyClassThree {
907917
@IsEnum(MyStringEnum)
908918
someProperty: MyStringEnum;
909919
}
910920

911921
it('should not fail if validator.validate said that its valid', () => {
912-
return checkValidValues(new MyClass(), validValues);
922+
return checkValidValues(new MyClassTwo(), validValues);
913923
});
914924

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

919929
it('should fail if validator.validate said that its invalid', () => {
920-
return checkInvalidValues(new MyClass(), invalidValues);
930+
return checkInvalidValues(new MyClassTwo(), invalidValues);
921931
});
922932

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

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

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

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

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

943-
it('should return error object with proper data', () => {
953+
it('should return error with proper message for default indexed enum', () => {
944954
const validationType = 'isEnum';
945-
const message = 'someProperty must be a valid enum value';
946-
return checkReturnedError(new MyClass(), invalidValues, validationType, message);
955+
const message = 'someProperty must be one of the following values: 0, 1';
956+
return checkReturnedError(new MyClassOne(), invalidValues, validationType, message);
957+
});
958+
959+
it('should return error with proper message for custom indexed enum', () => {
960+
const validationType = 'isEnum';
961+
const message = 'someProperty must be one of the following values: 1, 999';
962+
return checkReturnedError(new MyClassTwo(), invalidValues, validationType, message);
947963
});
948964

949-
it('should return error object with proper data (string enum)', () => {
965+
it('should return error with proper message for string enum', () => {
950966
const validationType = 'isEnum';
951-
const message = 'someProperty must be a valid enum value';
952-
checkReturnedError(new MyClass2(), invalidValues, validationType, message);
967+
const message = 'someProperty must be one of the following values: first, second';
968+
return checkReturnedError(new MyClassThree(), invalidValues, validationType, message);
953969
});
954970
});
955971

0 commit comments

Comments
 (0)