From f79282cb25e321d9433c235619c46896c4305cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Sch=C3=B8nemann?= Date: Wed, 9 Aug 2023 23:15:12 +0200 Subject: [PATCH 1/2] fix incorrect types generated when refs reference types with discriminators --- .../src/transform/schema-object.ts | 4 +- .../openapi-typescript/test/index.test.ts | 93 ++++++++++++++++++- .../test/schema-object.test.ts | 18 ++++ 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/packages/openapi-typescript/src/transform/schema-object.ts b/packages/openapi-typescript/src/transform/schema-object.ts index c05e77f15..4ed6ce08c 100644 --- a/packages/openapi-typescript/src/transform/schema-object.ts +++ b/packages/openapi-typescript/src/transform/schema-object.ts @@ -203,8 +203,8 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere // Mapping value can either be a fully-qualified ref (#/components/schemas/XYZ) or a schema name (XYZ) const matchedValue = Object.entries(discriminator.mapping).find(([, v]) => (!v.startsWith("#") && v === value) || (v.startsWith("#") && parseRef(v).path.pop() === value)); if (matchedValue) value = matchedValue[0]; // why was this designed backwards!? + coreType.unshift(indent(`${escObjKey(discriminator.propertyName)}: ${escStr(value)};`, indentLv + 1)); } - coreType.unshift(indent(`${escObjKey(discriminator.propertyName)}: ${escStr(value)};`, indentLv + 1)); break; } } @@ -217,7 +217,7 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere const output: string[] = []; for (const item of items) { const itemType = transformSchemaObject(item, { path, ctx: { ...ctx, indentLv } }); - if ("$ref" in item && ctx.discriminators[item.$ref]) { + if ("$ref" in item && ctx.discriminators[item.$ref]?.mapping) { output.push(tsOmit(itemType, [ctx.discriminators[item.$ref].propertyName])); continue; } diff --git a/packages/openapi-typescript/test/index.test.ts b/packages/openapi-typescript/test/index.test.ts index c10230136..83fcc34c7 100644 --- a/packages/openapi-typescript/test/index.test.ts +++ b/packages/openapi-typescript/test/index.test.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; -import type { OpenAPI3 } from "../src/types.js"; import openapiTS from "../dist/index.js"; +import type { OpenAPI3 } from "../src/types.js"; const BOILERPLATE = `/** * This file was auto-generated by openapi-typescript. @@ -457,7 +457,7 @@ export interface external { export type operations = Record; `); - }) + }); }); describe("3.1", () => { @@ -546,7 +546,7 @@ export type operations = Record; `); }); - test("discriminator (oneOf)", async () => { + test("discriminator with explicit mapping (oneOf)", async () => { const schema: OpenAPI3 = { openapi: "3.1", info: { title: "test", version: "1.0" }, @@ -625,6 +625,93 @@ export interface components { export type external = Record; +export type operations = Record; +`); + }); + + test("discriminator with implicit mapping (oneOf)", async () => { + const schema: OpenAPI3 = { + openapi: "3.1", + info: { title: "test", version: "1.0" }, + components: { + schemas: { + Pet: { + oneOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }, { $ref: "#/components/schemas/Lizard" }], + discriminator: { + propertyName: "petType", + }, + } as any, + Cat: { + type: "object", + properties: { + name: { type: "string" }, + petType: { type: "string", enum: ["cat"] }, + }, + required: ["petType"], + }, + Dog: { + type: "object", + properties: { + bark: { type: "string" }, + petType: { type: "string", enum: ["dog"] }, + }, + required: ["petType"], + }, + Lizard: { + type: "object", + properties: { + lovesRocks: { type: "boolean" }, + petType: { type: "string", enum: ["lizard"] }, + }, + required: ["petType"], + }, + Person: { + type: "object", + required: ["pet"], + properties: { + pet: { oneOf: [{ $ref: "#/components/schemas/Pet" }] }, + }, + }, + }, + }, + }; + const generated = await openapiTS(schema); + expect(generated).toBe(`${BOILERPLATE} +export type paths = Record; + +export type webhooks = Record; + +export interface components { + schemas: { + Pet: components["schemas"]["Cat"] | components["schemas"]["Dog"] | components["schemas"]["Lizard"]; + Cat: { + name?: string; + /** @enum {string} */ + petType: "cat"; + }; + Dog: { + bark?: string; + /** @enum {string} */ + petType: "dog"; + }; + Lizard: { + lovesRocks?: boolean; + /** @enum {string} */ + petType: "lizard"; + }; + Person: { + pet: components["schemas"]["Pet"]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} + +export type external = Record; + export type operations = Record; `); }); diff --git a/packages/openapi-typescript/test/schema-object.test.ts b/packages/openapi-typescript/test/schema-object.test.ts index 0753d5f84..881c75132 100644 --- a/packages/openapi-typescript/test/schema-object.test.ts +++ b/packages/openapi-typescript/test/schema-object.test.ts @@ -538,6 +538,24 @@ describe("Schema Object", () => { }`); }); + test("discriminator without mapping and oneOf and null", () => { + const schema: SchemaObject = { + oneOf: [{ $ref: 'components["schemas"]["parent"]' }, { type: "null" }], + }; + const generated = transformSchemaObject(schema, { + path: options.path, + ctx: { + ...options.ctx, + discriminators: { + 'components["schemas"]["parent"]': { + propertyName: "operation", + }, + }, + }, + }); + expect(generated).toBe(`components["schemas"]["parent"] | null`); + }); + test("discriminator escape", () => { const schema: SchemaObject = { type: "object", From 76f092f847049b071b9bb2532c992ebd62f4bec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Sch=C3=B8nemann?= Date: Wed, 9 Aug 2023 12:10:20 +0200 Subject: [PATCH 2/2] add changeset entry --- .changeset/olive-beers-raise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/olive-beers-raise.md diff --git a/.changeset/olive-beers-raise.md b/.changeset/olive-beers-raise.md new file mode 100644 index 000000000..657bd11d0 --- /dev/null +++ b/.changeset/olive-beers-raise.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript": patch +--- + +Fixed a bug where references to types with discriminators with implicit mappings would generate incorrect types