From 613e970cc1488e1beae72560c639fd23a99fa7bf Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Sun, 8 Jun 2025 18:37:40 +0300 Subject: [PATCH 1/2] fix(transform): avoid duplicate enum declaration when using root types with no prefix --- .../src/transform/components-object.ts | 3 + .../test/transform/schema-object/enum.test.ts | 136 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 packages/openapi-typescript/test/transform/schema-object/enum.test.ts diff --git a/packages/openapi-typescript/src/transform/components-object.ts b/packages/openapi-typescript/src/transform/components-object.ts index e5c100d26..863e349f6 100644 --- a/packages/openapi-typescript/src/transform/components-object.ts +++ b/packages/openapi-typescript/src/transform/components-object.ts @@ -80,6 +80,9 @@ export default function transformComponentsObject(componentsObject: ComponentsOb } const ref = ts.factory.createTypeReferenceNode(`components['${key}']['${name}']`); if (ctx.rootTypesNoSchemaPrefix && key === "schemas") { + if (ctx.enum && item.enum) { + continue; + } aliasName = aliasName.replace(componentKey, ""); } const typeAlias = ts.factory.createTypeAliasDeclaration( diff --git a/packages/openapi-typescript/test/transform/schema-object/enum.test.ts b/packages/openapi-typescript/test/transform/schema-object/enum.test.ts new file mode 100644 index 000000000..e828dea45 --- /dev/null +++ b/packages/openapi-typescript/test/transform/schema-object/enum.test.ts @@ -0,0 +1,136 @@ +import { fileURLToPath } from "node:url"; +import { transformSchema } from "../../../src/index.js"; +import { astToString } from "../../../src/lib/ts.js"; +import type { GlobalContext } from "../../../src/types.js"; +import { DEFAULT_CTX, type TestCase } from "../../test-helpers.js"; + +const DEFAULT_OPTIONS = DEFAULT_CTX; + +describe("transformComponentsObject", () => { + const tests: TestCase[] = [ + [ + "options > rootTypes: true and rootTypesNoSchemaPrefix: true", + { + given: { + "openapi": "3.0.0", + "info": { + "title": "Status API", + "version": "1.0.0" + }, + "paths": { + "/status": { + "get": { + "summary": "Get current status", + "responses": { + "200": { + "description": "Status response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "StatusResponse": { + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/Status" + } + } + }, + "Status": { + "type": "string", + "enum": ["pending", "active", "done"] + } + } + } + }, + want: `export interface paths { + "/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get current status */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Status response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatusResponse"]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + StatusResponse: { + status?: components["schemas"]["Status"]; + }; + /** @enum {string} */ + Status: Status; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type StatusResponse = components['schemas']['StatusResponse']; +export type $defs = Record; +export enum Status { + pending = "pending", + active = "active", + done = "done" +} +export type operations = Record;`, + options: { ...DEFAULT_OPTIONS, enum: true, rootTypes: true, rootTypesNoSchemaPrefix: true }, + }, + ], + ]; + + for (const [testName, { given, want, options, ci }] of tests) { + test.skipIf(ci?.skipIf)( + testName, + async () => { + const result = astToString(transformSchema(given, options ?? DEFAULT_OPTIONS)); + if (want instanceof URL) { + await expect(result).toMatchFileSnapshot(fileURLToPath(want)); + } else { + expect(result.trim()).toBe(want.trim()); + } + }, + ci?.timeout, + ); + } +}); From 39b374d17c23612460d08ac2e73c2e93b26181bc Mon Sep 17 00:00:00 2001 From: Eliya Cohen Date: Mon, 9 Jun 2025 10:19:08 +0300 Subject: [PATCH 2/2] format --- .../test/transform/schema-object/enum.test.ts | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/openapi-typescript/test/transform/schema-object/enum.test.ts b/packages/openapi-typescript/test/transform/schema-object/enum.test.ts index e828dea45..c9314850d 100644 --- a/packages/openapi-typescript/test/transform/schema-object/enum.test.ts +++ b/packages/openapi-typescript/test/transform/schema-object/enum.test.ts @@ -12,46 +12,46 @@ describe("transformComponentsObject", () => { "options > rootTypes: true and rootTypesNoSchemaPrefix: true", { given: { - "openapi": "3.0.0", - "info": { - "title": "Status API", - "version": "1.0.0" + openapi: "3.0.0", + info: { + title: "Status API", + version: "1.0.0", }, - "paths": { + paths: { "/status": { - "get": { - "summary": "Get current status", - "responses": { + get: { + summary: "Get current status", + responses: { "200": { - "description": "Status response", - "content": { + description: "Status response", + content: { "application/json": { - "schema": { - "$ref": "#/components/schemas/StatusResponse" - } - } - } - } - } - } - } + schema: { + $ref: "#/components/schemas/StatusResponse", + }, + }, + }, + }, + }, + }, + }, }, - "components": { - "schemas": { - "StatusResponse": { - "type": "object", - "properties": { - "status": { - "$ref": "#/components/schemas/Status" - } - } + components: { + schemas: { + StatusResponse: { + type: "object", + properties: { + status: { + $ref: "#/components/schemas/Status", + }, + }, }, - "Status": { - "type": "string", - "enum": ["pending", "active", "done"] - } - } - } + Status: { + type: "string", + enum: ["pending", "active", "done"], + }, + }, + }, }, want: `export interface paths { "/status": {