From 96dd2d864c2a0436cb5d78bada58ff82681d3045 Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Sat, 4 May 2024 23:20:42 +0200 Subject: [PATCH] feat(type): support mixed case enum member in union resolver --- packages/type/src/reflection/type.ts | 16 ++++++++++++++++ packages/type/src/serializer.ts | 18 ++++++------------ packages/type/tests/serializer.spec.ts | 4 ++++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/type/src/reflection/type.ts b/packages/type/src/reflection/type.ts index ecde23fe1..b056ebe0b 100644 --- a/packages/type/src/reflection/type.ts +++ b/packages/type/src/reflection/type.ts @@ -2332,6 +2332,22 @@ export function getDeepConstructorProperties(type: TypeClass): TypeParameter[] { return res; } +/** + * Returns the index to `type.values` if the given value is part of the enum, exactly or case-insensitive. + * Returns -1 if not found. + */ +export function getEnumValueIndexMatcher(type: TypeEnum): (value: string | number | undefined | null) => number { + const lowerCaseValues = Object.keys(type.enum).map(v => String(v).toLowerCase()); + return (value): number => { + const exactMatch = type.values.indexOf(value); + if (exactMatch !== -1) return exactMatch; + const lowerCaseMatch = lowerCaseValues.indexOf(String(value).toLowerCase()); + if (lowerCaseMatch !== -1) return lowerCaseMatch; + + return -1; + }; +} + interface StringifyTypeOptions { //show type alias names showNames: boolean; diff --git a/packages/type/src/serializer.ts b/packages/type/src/serializer.ts index a9e95ba9b..446960f87 100644 --- a/packages/type/src/serializer.ts +++ b/packages/type/src/serializer.ts @@ -38,6 +38,7 @@ import { FindType, getConstructorProperties, getDeepConstructorProperties, + getEnumValueIndexMatcher, getTypeJitContainer, getTypeObjectLiteralFromTypeClass, groupAnnotation, @@ -1977,17 +1978,10 @@ export class Serializer { this.serializeRegistry.register(ReflectionKind.enum, (type, state) => state.addSetter(state.accessor)); this.deserializeRegistry.register(ReflectionKind.enum, (type, state) => { const valuesVar = state.setVariable('values', type.values); - const lowercaseNames = state.setVariable('lowercaseNames', Object.keys(type.enum).map(v => v.toLowerCase())); - const allLowercased = Object.keys(type.enum).every(v => v.toLowerCase() === v); - const enumValues = state.setVariable('enumValues', type.values); - const allowLowercase = allLowercased ? '' : ` - ${state.accessor} = ${enumValues}[${lowercaseNames}.indexOf(String(${state.accessor}).toLowerCase())] ?? ${state.accessor}; - `; - + const matcher = state.setVariable('enumMatcher', getEnumValueIndexMatcher(type)); state.addCodeForSetter(` - ${allowLowercase} - ${state.setter} = ${state.accessor}; - if (${valuesVar}.indexOf(${state.accessor}) === -1) ${state.throwCode('enum', `'No valid value of ' + ${valuesVar}.join(', ')`)} + ${state.setter} = ${valuesVar}[${matcher}(${state.accessor})]; + if (${valuesVar}.indexOf(${state.setter}) === -1) ${state.throwCode('enum', `'No valid value of ' + ${valuesVar}.join(', ')`)} `); }); @@ -2219,8 +2213,8 @@ export class Serializer { this.typeGuards.register(1, ReflectionKind.promise, (type, state) => executeTemplates(state, type.type)); this.typeGuards.register(1, ReflectionKind.enum, (type, state) => { - const values = state.setVariable('values', type.values); - state.addSetterAndReportErrorIfInvalid('type', 'Invalid enum member', `${values}.indexOf(${state.accessor}) >= 0`); + const matcher = state.setVariable('enumMatcher', getEnumValueIndexMatcher(type)); + state.addSetterAndReportErrorIfInvalid('type', 'Invalid enum member', `${matcher}(${state.accessor}) >= 0`); }); this.typeGuards.register(1, ReflectionKind.array, (type, state) => typeGuardArray(type.type, state)); this.typeGuards.register(1, ReflectionKind.tuple, typeGuardTuple); diff --git a/packages/type/tests/serializer.spec.ts b/packages/type/tests/serializer.spec.ts index a22c4a0f0..f6795b996 100644 --- a/packages/type/tests/serializer.spec.ts +++ b/packages/type/tests/serializer.spec.ts @@ -1033,6 +1033,10 @@ test('enum mixed case', () => { expect(cast('gram')).toBe('g'); expect(cast('gram')).toBe(Units.GRAM); + + expect(cast('GRAM')).toBe(Units.GRAM); + expect(cast(23)).toBe(23); + expect(cast('Gram')).toBe(Units.GRAM); }); test('enum union', () => {