Skip to content

Commit 7f452fa

Browse files
fix incorrect types generated when refs reference types with discriminators (#1289)
* fix incorrect types generated when refs reference types with discriminators * add changeset entry
1 parent 8412147 commit 7f452fa

File tree

4 files changed

+115
-5
lines changed

4 files changed

+115
-5
lines changed

.changeset/olive-beers-raise.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"openapi-typescript": patch
3+
---
4+
5+
Fixed a bug where references to types with discriminators with implicit mappings would generate incorrect types

packages/openapi-typescript/src/transform/schema-object.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere
203203
// Mapping value can either be a fully-qualified ref (#/components/schemas/XYZ) or a schema name (XYZ)
204204
const matchedValue = Object.entries(discriminator.mapping).find(([, v]) => (!v.startsWith("#") && v === value) || (v.startsWith("#") && parseRef(v).path.pop() === value));
205205
if (matchedValue) value = matchedValue[0]; // why was this designed backwards!?
206+
coreType.unshift(indent(`${escObjKey(discriminator.propertyName)}: ${escStr(value)};`, indentLv + 1));
206207
}
207-
coreType.unshift(indent(`${escObjKey(discriminator.propertyName)}: ${escStr(value)};`, indentLv + 1));
208208
break;
209209
}
210210
}
@@ -217,7 +217,7 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere
217217
const output: string[] = [];
218218
for (const item of items) {
219219
const itemType = transformSchemaObject(item, { path, ctx: { ...ctx, indentLv } });
220-
if ("$ref" in item && ctx.discriminators[item.$ref]) {
220+
if ("$ref" in item && ctx.discriminators[item.$ref]?.mapping) {
221221
output.push(tsOmit(itemType, [ctx.discriminators[item.$ref].propertyName]));
222222
continue;
223223
}

packages/openapi-typescript/test/index.test.ts

+90-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from "node:fs";
2-
import type { OpenAPI3 } from "../src/types.js";
32
import openapiTS from "../dist/index.js";
3+
import type { OpenAPI3 } from "../src/types.js";
44

55
const BOILERPLATE = `/**
66
* This file was auto-generated by openapi-typescript.
@@ -457,7 +457,7 @@ export interface external {
457457
458458
export type operations = Record<string, never>;
459459
`);
460-
})
460+
});
461461
});
462462

463463
describe("3.1", () => {
@@ -546,7 +546,7 @@ export type operations = Record<string, never>;
546546
`);
547547
});
548548

549-
test("discriminator (oneOf)", async () => {
549+
test("discriminator with explicit mapping (oneOf)", async () => {
550550
const schema: OpenAPI3 = {
551551
openapi: "3.1",
552552
info: { title: "test", version: "1.0" },
@@ -625,6 +625,93 @@ export interface components {
625625
626626
export type external = Record<string, never>;
627627
628+
export type operations = Record<string, never>;
629+
`);
630+
});
631+
632+
test("discriminator with implicit mapping (oneOf)", async () => {
633+
const schema: OpenAPI3 = {
634+
openapi: "3.1",
635+
info: { title: "test", version: "1.0" },
636+
components: {
637+
schemas: {
638+
Pet: {
639+
oneOf: [{ $ref: "#/components/schemas/Cat" }, { $ref: "#/components/schemas/Dog" }, { $ref: "#/components/schemas/Lizard" }],
640+
discriminator: {
641+
propertyName: "petType",
642+
},
643+
} as any,
644+
Cat: {
645+
type: "object",
646+
properties: {
647+
name: { type: "string" },
648+
petType: { type: "string", enum: ["cat"] },
649+
},
650+
required: ["petType"],
651+
},
652+
Dog: {
653+
type: "object",
654+
properties: {
655+
bark: { type: "string" },
656+
petType: { type: "string", enum: ["dog"] },
657+
},
658+
required: ["petType"],
659+
},
660+
Lizard: {
661+
type: "object",
662+
properties: {
663+
lovesRocks: { type: "boolean" },
664+
petType: { type: "string", enum: ["lizard"] },
665+
},
666+
required: ["petType"],
667+
},
668+
Person: {
669+
type: "object",
670+
required: ["pet"],
671+
properties: {
672+
pet: { oneOf: [{ $ref: "#/components/schemas/Pet" }] },
673+
},
674+
},
675+
},
676+
},
677+
};
678+
const generated = await openapiTS(schema);
679+
expect(generated).toBe(`${BOILERPLATE}
680+
export type paths = Record<string, never>;
681+
682+
export type webhooks = Record<string, never>;
683+
684+
export interface components {
685+
schemas: {
686+
Pet: components["schemas"]["Cat"] | components["schemas"]["Dog"] | components["schemas"]["Lizard"];
687+
Cat: {
688+
name?: string;
689+
/** @enum {string} */
690+
petType: "cat";
691+
};
692+
Dog: {
693+
bark?: string;
694+
/** @enum {string} */
695+
petType: "dog";
696+
};
697+
Lizard: {
698+
lovesRocks?: boolean;
699+
/** @enum {string} */
700+
petType: "lizard";
701+
};
702+
Person: {
703+
pet: components["schemas"]["Pet"];
704+
};
705+
};
706+
responses: never;
707+
parameters: never;
708+
requestBodies: never;
709+
headers: never;
710+
pathItems: never;
711+
}
712+
713+
export type external = Record<string, never>;
714+
628715
export type operations = Record<string, never>;
629716
`);
630717
});

packages/openapi-typescript/test/schema-object.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,24 @@ describe("Schema Object", () => {
538538
}`);
539539
});
540540

541+
test("discriminator without mapping and oneOf and null", () => {
542+
const schema: SchemaObject = {
543+
oneOf: [{ $ref: 'components["schemas"]["parent"]' }, { type: "null" }],
544+
};
545+
const generated = transformSchemaObject(schema, {
546+
path: options.path,
547+
ctx: {
548+
...options.ctx,
549+
discriminators: {
550+
'components["schemas"]["parent"]': {
551+
propertyName: "operation",
552+
},
553+
},
554+
},
555+
});
556+
expect(generated).toBe(`components["schemas"]["parent"] | null`);
557+
});
558+
541559
test("discriminator escape", () => {
542560
const schema: SchemaObject = {
543561
type: "object",

0 commit comments

Comments
 (0)