Skip to content

Commit

Permalink
fix incorrect types generated when refs reference types with discrimi…
Browse files Browse the repository at this point in the history
…nators (#1289)

* fix incorrect types generated when refs reference types with discriminators

* add changeset entry
  • Loading branch information
adamschoenemann authored Aug 10, 2023
1 parent 8412147 commit 7f452fa
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-beers-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"openapi-typescript": patch
---

Fixed a bug where references to types with discriminators with implicit mappings would generate incorrect types
4 changes: 2 additions & 2 deletions packages/openapi-typescript/src/transform/schema-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand All @@ -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;
}
Expand Down
93 changes: 90 additions & 3 deletions packages/openapi-typescript/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -457,7 +457,7 @@ export interface external {
export type operations = Record<string, never>;
`);
})
});
});

describe("3.1", () => {
Expand Down Expand Up @@ -546,7 +546,7 @@ export type operations = Record<string, never>;
`);
});

test("discriminator (oneOf)", async () => {
test("discriminator with explicit mapping (oneOf)", async () => {
const schema: OpenAPI3 = {
openapi: "3.1",
info: { title: "test", version: "1.0" },
Expand Down Expand Up @@ -625,6 +625,93 @@ export interface components {
export type external = Record<string, never>;
export type operations = Record<string, never>;
`);
});

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<string, never>;
export type webhooks = Record<string, never>;
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<string, never>;
export type operations = Record<string, never>;
`);
});
Expand Down
18 changes: 18 additions & 0 deletions packages/openapi-typescript/test/schema-object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 7f452fa

Please sign in to comment.