From f599f441a32806d6728a51009d2db44e0296167e Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:05:20 +0200 Subject: [PATCH 1/4] fix(core): generate correct enum types when combining enums --- packages/core/src/getters/combine.ts | 64 ++++++++++++++++++++-------- packages/core/src/getters/enum.ts | 21 +++++++-- tests/configs/default.config.ts | 53 ++++++++++++++++++++--- 3 files changed, 111 insertions(+), 27 deletions(-) diff --git a/packages/core/src/getters/combine.ts b/packages/core/src/getters/combine.ts index 6907019de..e2f9aa58e 100644 --- a/packages/core/src/getters/combine.ts +++ b/packages/core/src/getters/combine.ts @@ -2,13 +2,14 @@ import { SchemaObject } from 'openapi3-ts/oas30'; import { resolveExampleRefs, resolveObject } from '../resolvers'; import { ContextSpecs, + EnumGeneration, GeneratorImport, GeneratorSchema, ScalarValue, SchemaType, } from '../types'; import { getNumberWord, pascal, isSchema } from '../utils'; -import { getEnumImplementation, getEnumNames } from './enum'; +import { getEnumItems, getEnumNames } from './enum'; import { getScalar } from './scalar'; import uniq from 'lodash.uniq'; @@ -191,12 +192,18 @@ export const combineSchemas = ({ const isAllEnums = resolvedData.isEnum.every((v) => v); if (isAllEnums && name && items.length > 1) { - const newEnum = `// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const ${pascal( + const newEnum = getCombineEnumValue( + resolvedData, name, - )} = ${getCombineEnumValue(resolvedData)}`; + context.output.override.enumGenerationType, + ); + const propertyType = getEnumPropertyType( + pascal(name), + context.output.override.enumGenerationType, + ); return { - value: `typeof ${pascal(name)}[keyof typeof ${pascal(name)}] ${nullable}`, + value: propertyType, imports: [ { name: pascal(name), @@ -212,7 +219,7 @@ export const combineSchemas = ({ })), ], model: newEnum, - name: name, + name: pascal(name), }, ], isEnum: false, @@ -263,17 +270,40 @@ export const combineSchemas = ({ }; }; -const getCombineEnumValue = ({ - values, - isRef, - originalSchema, -}: CombinedData) => { - if (values.length === 1) { - if (isRef[0]) { - return values[0]; - } +const getEnumImplementation = ( + enumValue: string, + enumName: string, + enumGenerationType: EnumGeneration, +) => { + if (enumGenerationType === EnumGeneration.CONST) + return `// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const ${enumName} = {${enumValue}} as const`; + if (enumGenerationType === EnumGeneration.ENUM) + return `export enum ${enumName} {${enumValue}}`; + if (enumGenerationType === EnumGeneration.UNION) + return `export type ${enumName} = ${enumValue}`; + throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); +}; + +const getEnumPropertyType = ( + enumName: string, + enumGenerationType: EnumGeneration, +) => { + if (enumGenerationType === EnumGeneration.CONST) + return `typeof ${enumName}[keyof typeof ${enumName}]`; + if (enumGenerationType === EnumGeneration.ENUM) return enumName; + if (enumGenerationType === EnumGeneration.UNION) return enumName; + throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); +}; - return `{${getEnumImplementation(values[0])}} as const`; +const getCombineEnumValue = ( + { values, isRef, originalSchema }: CombinedData, + name: string, + enumGenerationType: EnumGeneration, +): string => { + if (values.length === 1) { + const names = getEnumNames(originalSchema[0]); + const items = getEnumItems(values[0], names, enumGenerationType); + return getEnumImplementation(items, name, enumGenerationType); } const enums = values @@ -284,9 +314,9 @@ const getCombineEnumValue = ({ const names = getEnumNames(originalSchema[i]); - return getEnumImplementation(e, names); + return getEnumItems(e, names, enumGenerationType); }) .join(''); - return `{${enums}} as const`; + return getEnumImplementation(enums, name, enumGenerationType); }; diff --git a/packages/core/src/getters/enum.ts b/packages/core/src/getters/enum.ts index f022ce488..1e051480d 100644 --- a/packages/core/src/getters/enum.ts +++ b/packages/core/src/getters/enum.ts @@ -3,7 +3,9 @@ import { SchemaObject } from 'openapi3-ts/dist/model/openapi30'; import { EnumGeneration } from '../types'; import { isNumeric, sanitize } from '../utils'; -export const getEnumNames = (schemaObject: SchemaObject | undefined) => { +export const getEnumNames = ( + schemaObject: SchemaObject | undefined, +): string[] | undefined => { return ( schemaObject?.['x-enumNames'] || schemaObject?.['x-enumnames'] || @@ -26,6 +28,19 @@ export const getEnum = ( throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); }; +export const getEnumItems = ( + value: string, + names: string[] | undefined, + enumGenerationType: EnumGeneration, +) => { + if (enumGenerationType === EnumGeneration.CONST) + return getConstEnumItems(value, names); + if (enumGenerationType === EnumGeneration.ENUM) + return getNativeEnumItems(value, names); + if (enumGenerationType === EnumGeneration.UNION) return value; + throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); +}; + const getTypeConstEnum = ( value: string, enumName: string, @@ -40,7 +55,7 @@ const getTypeConstEnum = ( enumValue += ';\n'; - const implementation = getEnumImplementation(value, names); + const implementation = getConstEnumItems(value, names); enumValue += `\n\n`; @@ -51,7 +66,7 @@ const getTypeConstEnum = ( return enumValue; }; -export const getEnumImplementation = (value: string, names?: string[]) => { +const getConstEnumItems = (value: string, names?: string[]) => { // empty enum or null-only enum if (value === '') return ''; diff --git a/tests/configs/default.config.ts b/tests/configs/default.config.ts index 6d2e52880..99da3b336 100644 --- a/tests/configs/default.config.ts +++ b/tests/configs/default.config.ts @@ -390,18 +390,57 @@ export default defineConfig({ target: '../specifications/enums.yaml', }, }, - formDataExplode: { + constCombineEnums: { output: { - target: '../generated/default/form-data-explode/endpoints.ts', - schemas: '../generated/default/form-data-explode/model', + target: '../generated/default/enums/combine/const/endpoints.ts', + schemas: '../generated/default/enums/combine/const/model', + mock: true, override: { - formData: { - arrayHandling: 'explode', - }, + enumGenerationType: 'const', }, }, input: { - target: '../specifications/form-data-nested.yaml', + target: '../specifications/combine-enums.yaml', }, }, + nativeCombineEnums: { + output: { + target: '../generated/default/enums/combine/native/endpoints.ts', + schemas: '../generated/default/enums/combine/native/model', + mock: true, + override: { + enumGenerationType: 'enum', + }, + }, + input: { + target: '../specifications/combine-enums.yaml', + }, + }, + // unionCombineEnums: { + // output: { + // target: '../generated/default/enums/combine/union/endpoints.ts', + // schemas: '../generated/default/enums/combine/union/model', + // mock: true, + // override: { + // enumGenerationType: 'union', + // }, + // }, + // input: { + // target: '../specifications/combine-enums.yaml', + // }, + // }, + // formDataExplode: { + // output: { + // target: '../generated/default/form-data-explode/endpoints.ts', + // schemas: '../generated/default/form-data-explode/model', + // override: { + // formData: { + // arrayHandling: 'explode', + // }, + // }, + // }, + // input: { + // target: '../specifications/form-data-nested.yaml', + // }, + // }, }); From 0b52d5662c3b0dbb51f9815b137451b6fa35d779 Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:08:24 +0200 Subject: [PATCH 2/4] fix(core): move enum handling to enum.ts, add test --- packages/core/src/getters/combine.ts | 36 ++++------------- packages/core/src/getters/enum.ts | 27 ++++++++++++- tests/configs/default.config.ts | 54 ++++++++++++------------- tests/specifications/combine-enums.yaml | 23 +++++++++++ 4 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 tests/specifications/combine-enums.yaml diff --git a/packages/core/src/getters/combine.ts b/packages/core/src/getters/combine.ts index e2f9aa58e..582c31c9e 100644 --- a/packages/core/src/getters/combine.ts +++ b/packages/core/src/getters/combine.ts @@ -9,7 +9,12 @@ import { SchemaType, } from '../types'; import { getNumberWord, pascal, isSchema } from '../utils'; -import { getEnumItems, getEnumNames } from './enum'; +import { + getEnumDefinition, + getEnumItems, + getEnumNames, + getEnumPropertyType, +} from './enum'; import { getScalar } from './scalar'; import uniq from 'lodash.uniq'; @@ -270,31 +275,6 @@ export const combineSchemas = ({ }; }; -const getEnumImplementation = ( - enumValue: string, - enumName: string, - enumGenerationType: EnumGeneration, -) => { - if (enumGenerationType === EnumGeneration.CONST) - return `// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const ${enumName} = {${enumValue}} as const`; - if (enumGenerationType === EnumGeneration.ENUM) - return `export enum ${enumName} {${enumValue}}`; - if (enumGenerationType === EnumGeneration.UNION) - return `export type ${enumName} = ${enumValue}`; - throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); -}; - -const getEnumPropertyType = ( - enumName: string, - enumGenerationType: EnumGeneration, -) => { - if (enumGenerationType === EnumGeneration.CONST) - return `typeof ${enumName}[keyof typeof ${enumName}]`; - if (enumGenerationType === EnumGeneration.ENUM) return enumName; - if (enumGenerationType === EnumGeneration.UNION) return enumName; - throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); -}; - const getCombineEnumValue = ( { values, isRef, originalSchema }: CombinedData, name: string, @@ -303,7 +283,7 @@ const getCombineEnumValue = ( if (values.length === 1) { const names = getEnumNames(originalSchema[0]); const items = getEnumItems(values[0], names, enumGenerationType); - return getEnumImplementation(items, name, enumGenerationType); + return getEnumDefinition(items, name, enumGenerationType); } const enums = values @@ -318,5 +298,5 @@ const getCombineEnumValue = ( }) .join(''); - return getEnumImplementation(enums, name, enumGenerationType); + return getEnumDefinition(enums, name, enumGenerationType); }; diff --git a/packages/core/src/getters/enum.ts b/packages/core/src/getters/enum.ts index 1e051480d..6396edb41 100644 --- a/packages/core/src/getters/enum.ts +++ b/packages/core/src/getters/enum.ts @@ -38,7 +38,32 @@ export const getEnumItems = ( if (enumGenerationType === EnumGeneration.ENUM) return getNativeEnumItems(value, names); if (enumGenerationType === EnumGeneration.UNION) return value; - throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); + return ''; +}; + +export const getEnumDefinition = ( + enumValue: string, + enumName: string, + enumGenerationType: EnumGeneration, +) => { + if (enumGenerationType === EnumGeneration.CONST) + return `// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const ${enumName} = {${enumValue}} as const`; + if (enumGenerationType === EnumGeneration.ENUM) + return `export enum ${enumName} {${enumValue}}`; + if (enumGenerationType === EnumGeneration.UNION) + return `export type ${enumName} = ${enumValue}`; + return ''; +}; + +export const getEnumPropertyType = ( + enumName: string, + enumGenerationType: EnumGeneration, +) => { + if (enumGenerationType === EnumGeneration.CONST) + return `typeof ${enumName}[keyof typeof ${enumName}]`; + if (enumGenerationType === EnumGeneration.ENUM) return enumName; + if (enumGenerationType === EnumGeneration.UNION) return enumName; + return ''; }; const getTypeConstEnum = ( diff --git a/tests/configs/default.config.ts b/tests/configs/default.config.ts index 99da3b336..6c4ed7437 100644 --- a/tests/configs/default.config.ts +++ b/tests/configs/default.config.ts @@ -416,31 +416,31 @@ export default defineConfig({ target: '../specifications/combine-enums.yaml', }, }, - // unionCombineEnums: { - // output: { - // target: '../generated/default/enums/combine/union/endpoints.ts', - // schemas: '../generated/default/enums/combine/union/model', - // mock: true, - // override: { - // enumGenerationType: 'union', - // }, - // }, - // input: { - // target: '../specifications/combine-enums.yaml', - // }, - // }, - // formDataExplode: { - // output: { - // target: '../generated/default/form-data-explode/endpoints.ts', - // schemas: '../generated/default/form-data-explode/model', - // override: { - // formData: { - // arrayHandling: 'explode', - // }, - // }, - // }, - // input: { - // target: '../specifications/form-data-nested.yaml', - // }, - // }, + unionCombineEnums: { + output: { + target: '../generated/default/enums/combine/union/endpoints.ts', + schemas: '../generated/default/enums/combine/union/model', + mock: true, + override: { + enumGenerationType: 'union', + }, + }, + input: { + target: '../specifications/combine-enums.yaml', + }, + }, + formDataExplode: { + output: { + target: '../generated/default/form-data-explode/endpoints.ts', + schemas: '../generated/default/form-data-explode/model', + override: { + formData: { + arrayHandling: 'explode', + }, + }, + }, + input: { + target: '../specifications/form-data-nested.yaml', + }, + }, }); diff --git a/tests/specifications/combine-enums.yaml b/tests/specifications/combine-enums.yaml new file mode 100644 index 000000000..bc57d509a --- /dev/null +++ b/tests/specifications/combine-enums.yaml @@ -0,0 +1,23 @@ +openapi: 3.1.0 +info: + title: Combine Enums + version: 1.0.0 +paths: + /api: + get: + responses: + '200': + description: Example response + content: + application/json: + schema: + type: object + properties: + foo: + anyOf: + - type: string + enum: + - example-true + - type: string + enum: + - example-false From 10d3adc1f4daaf8ab7421780f8501a4cc4fb1098 Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:51:31 +0200 Subject: [PATCH 3/4] fix: move methods to enum.ts --- packages/core/src/getters/combine.ts | 38 +++------------------------- packages/core/src/getters/enum.ts | 38 ++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/core/src/getters/combine.ts b/packages/core/src/getters/combine.ts index 582c31c9e..2d64a6d6f 100644 --- a/packages/core/src/getters/combine.ts +++ b/packages/core/src/getters/combine.ts @@ -1,22 +1,16 @@ +import uniq from 'lodash.uniq'; import { SchemaObject } from 'openapi3-ts/oas30'; import { resolveExampleRefs, resolveObject } from '../resolvers'; import { ContextSpecs, - EnumGeneration, GeneratorImport, GeneratorSchema, ScalarValue, SchemaType, } from '../types'; -import { getNumberWord, pascal, isSchema } from '../utils'; -import { - getEnumDefinition, - getEnumItems, - getEnumNames, - getEnumPropertyType, -} from './enum'; +import { getNumberWord, isSchema, pascal } from '../utils'; +import { getCombineEnumValue, getEnumPropertyType } from './enum'; import { getScalar } from './scalar'; -import uniq from 'lodash.uniq'; type CombinedData = { imports: GeneratorImport[]; @@ -274,29 +268,3 @@ export const combineSchemas = ({ examples: resolveExampleRefs(schema.examples, context), }; }; - -const getCombineEnumValue = ( - { values, isRef, originalSchema }: CombinedData, - name: string, - enumGenerationType: EnumGeneration, -): string => { - if (values.length === 1) { - const names = getEnumNames(originalSchema[0]); - const items = getEnumItems(values[0], names, enumGenerationType); - return getEnumDefinition(items, name, enumGenerationType); - } - - const enums = values - .map((e, i) => { - if (isRef[i]) { - return `...${e},`; - } - - const names = getEnumNames(originalSchema[i]); - - return getEnumItems(e, names, enumGenerationType); - }) - .join(''); - - return getEnumDefinition(enums, name, enumGenerationType); -}; diff --git a/packages/core/src/getters/enum.ts b/packages/core/src/getters/enum.ts index 6396edb41..ac2236183 100644 --- a/packages/core/src/getters/enum.ts +++ b/packages/core/src/getters/enum.ts @@ -28,7 +28,7 @@ export const getEnum = ( throw new Error(`Invalid enumGenerationType: ${enumGenerationType}`); }; -export const getEnumItems = ( +const getEnumItems = ( value: string, names: string[] | undefined, enumGenerationType: EnumGeneration, @@ -41,7 +41,7 @@ export const getEnumItems = ( return ''; }; -export const getEnumDefinition = ( +const getEnumDefinition = ( enumValue: string, enumName: string, enumGenerationType: EnumGeneration, @@ -66,6 +66,40 @@ export const getEnumPropertyType = ( return ''; }; +export const getCombineEnumValue = ( + { + values, + isRef, + originalSchema, + }: { + values: string[]; + isRef: boolean[]; + originalSchema: (SchemaObject | undefined)[]; + }, + name: string, + enumGenerationType: EnumGeneration, +): string => { + if (values.length === 1) { + const names = getEnumNames(originalSchema[0]); + const items = getEnumItems(values[0], names, enumGenerationType); + return getEnumDefinition(items, name, enumGenerationType); + } + + const enums = values + .map((e, i) => { + if (isRef[i]) { + return `...${e},`; + } + + const names = getEnumNames(originalSchema[i]); + + return getEnumItems(e, names, enumGenerationType); + }) + .join(''); + + return getEnumDefinition(enums, name, enumGenerationType); +}; + const getTypeConstEnum = ( value: string, enumName: string, From a04098b3b310a9b39a69435fac64ec5594dc010c Mon Sep 17 00:00:00 2001 From: Alice Jonsson <10475857+AllieJonsson@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:23:02 +0200 Subject: [PATCH 4/4] fix(core): cast to any when mocking native enum --- packages/mock/src/faker/getters/scalar.ts | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/packages/mock/src/faker/getters/scalar.ts b/packages/mock/src/faker/getters/scalar.ts index d4557ae63..bdf0d43a6 100644 --- a/packages/mock/src/faker/getters/scalar.ts +++ b/packages/mock/src/faker/getters/scalar.ts @@ -337,26 +337,7 @@ const getEnum = ( let enumValue = `[${joindEnumValues}]`; if (context.output.override.enumGenerationType === EnumGeneration.ENUM) { - if (item.isRef || existingReferencedProperties.length === 0) { - enumValue += ` as ${item.name}${item.name.endsWith('[]') ? '' : '[]'}`; - imports.push({ - name: item.name, - ...(!isRootKey(context.specKey, context.target) - ? { specKey: context.specKey } - : {}), - }); - } else { - enumValue += ` as ${existingReferencedProperties[existingReferencedProperties.length - 1]}['${item.name}']`; - if (!item.path?.endsWith('[]')) enumValue += '[]'; - imports.push({ - name: existingReferencedProperties[ - existingReferencedProperties.length - 1 - ], - ...(!isRootKey(context.specKey, context.target) - ? { specKey: context.specKey } - : {}), - }); - } + enumValue += ' as any[]'; } else { enumValue += ' as const'; }