Skip to content

Commit 1f6dea4

Browse files
committed
Fixed base class discriminators to only be handled in allOfs
1 parent 866ad55 commit 1f6dea4

File tree

5 files changed

+60
-26
lines changed

5 files changed

+60
-26
lines changed

packages/openapi-typescript/examples/digital-ocean-api.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -19323,7 +19323,7 @@ export interface operations {
1932319323
* */
1932419324
requestBody?: {
1932519325
content: {
19326-
"application/json": Omit<components["schemas"]["floating_ip_action_unassign"], "type"> | Omit<components["schemas"]["floating_ip_action_assign"], "type">;
19326+
"application/json": components["schemas"]["floating_ip_action_unassign"] | components["schemas"]["floating_ip_action_assign"];
1932719327
};
1932819328
};
1932919329
responses: {
@@ -22367,7 +22367,7 @@ export interface operations {
2236722367
* */
2236822368
requestBody?: {
2236922369
content: {
22370-
"application/json": Omit<components["schemas"]["reserved_ip_action_unassign"], "type"> | Omit<components["schemas"]["reserved_ip_action_assign"], "type">;
22370+
"application/json": components["schemas"]["reserved_ip_action_unassign"] | components["schemas"]["reserved_ip_action_assign"];
2237122371
};
2237222372
};
2237322373
responses: {

packages/openapi-typescript/src/lib/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ export function scanDiscriminators(
326326
// (sometimes this mapping is implicit, so it can’t be done until we know
327327
// about every discriminator in the document)
328328
walk(schema, (obj, path) => {
329-
for (const key of ["oneOf", "anyOf", "allOf"] as const) {
329+
for (const key of ["allOf"] as const) {
330330
if (obj && Array.isArray(obj[key])) {
331331
for (const item of (obj as any)[key]) {
332332
if ("$ref" in item) {

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

+18-11
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,18 @@ export function transformSchemaObjectWithComposition(
138138
* Object + composition (anyOf/allOf/oneOf) types
139139
*/
140140

141-
/** Collect oneOf/allOf/anyOf with Omit<> for discriminators */
142-
function collectCompositions(
141+
/** Collect oneOf/anyOf */
142+
function collectUnionCompositions(items: (SchemaObject | ReferenceObject)[]) {
143+
const output: ts.TypeNode[] = [];
144+
for (const item of items) {
145+
output.push(transformSchemaObject(item, options));
146+
}
147+
148+
return output;
149+
}
150+
151+
/** Collect allOf with Omit<> for discriminators */
152+
function collectAllOfCompositions(
143153
items: (SchemaObject | ReferenceObject)[],
144154
required?: string[],
145155
): ts.TypeNode[] {
@@ -184,6 +194,7 @@ export function transformSchemaObjectWithComposition(
184194
options,
185195
);
186196
}
197+
187198
const discriminator =
188199
("$ref" in item && options.ctx.discriminators.objects[item.$ref]) ||
189200
(item as any).discriminator; // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -201,7 +212,7 @@ export function transformSchemaObjectWithComposition(
201212

202213
// core + allOf: intersect
203214
const coreObjectType = transformSchemaObjectCore(schemaObject, options);
204-
const allOfType = collectCompositions(
215+
const allOfType = collectAllOfCompositions(
205216
schemaObject.allOf ?? [],
206217
schemaObject.required,
207218
);
@@ -216,21 +227,17 @@ export function transformSchemaObjectWithComposition(
216227
}
217228
// anyOf: union
218229
// (note: this may seem counterintuitive, but as TypeScript’s unions are not true XORs, they mimic behavior closer to anyOf than oneOf)
219-
const anyOfType = collectCompositions(
220-
schemaObject.anyOf ?? [],
221-
schemaObject.required,
222-
);
230+
const anyOfType = collectUnionCompositions(schemaObject.anyOf ?? []);
223231
if (anyOfType.length) {
224232
finalType = tsUnion([...(finalType ? [finalType] : []), ...anyOfType]);
225233
}
226234
// oneOf: union (within intersection with other types, if any)
227-
const oneOfType = collectCompositions(
235+
const oneOfType = collectUnionCompositions(
228236
schemaObject.oneOf ||
229237
("type" in schemaObject &&
230238
schemaObject.type === "object" &&
231239
(schemaObject.enum as (SchemaObject | ReferenceObject)[])) ||
232240
[],
233-
schemaObject.required,
234241
);
235242
if (oneOfType.length) {
236243
// note: oneOf is the only type that may include primitives
@@ -408,8 +415,8 @@ function transformSchemaObjectCore(
408415
// type: object
409416
const coreObjectType: ts.TypeElement[] = [];
410417

411-
// discriminatorss: explicit mapping on schema object
412-
for (const k of ["oneOf", "allOf", "anyOf"] as const) {
418+
// discriminators: explicit mapping on schema object
419+
for (const k of ["allOf", "anyOf"] as const) {
413420
if (!schemaObject[k]) {
414421
continue;
415422
}

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

+37
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,38 @@ describe("3.1 discriminators", () => {
4343
},
4444
allOf: [{ $ref: "#/components/schemas/Pet" }],
4545
},
46+
LizardDog: {
47+
allOf: [
48+
{ $ref: "#/components/schemas/Dog" },
49+
{ $ref: "#/components/schemas/Lizard" },
50+
],
51+
},
52+
AnimalSighting: {
53+
oneOf: [
54+
{
55+
$ref: "#/components/schemas/Cat",
56+
},
57+
{
58+
$ref: "#/components/schemas/Dog",
59+
},
60+
{
61+
$ref: "#/components/schemas/Lizard",
62+
},
63+
],
64+
},
65+
Beast: {
66+
anyOf: [
67+
{
68+
$ref: "#/components/schemas/Cat",
69+
},
70+
{
71+
$ref: "#/components/schemas/Dog",
72+
},
73+
{
74+
$ref: "#/components/schemas/Lizard",
75+
},
76+
],
77+
},
4678
},
4779
},
4880
},
@@ -65,6 +97,11 @@ export interface components {
6597
petType: "Lizard";
6698
lovesRocks?: boolean;
6799
} & Omit<components["schemas"]["Pet"], "petType">;
100+
LizardDog: {
101+
petType: "LizardDog";
102+
} & (Omit<components["schemas"]["Dog"], "petType"> & Omit<components["schemas"]["Lizard"], "petType">);
103+
AnimalSighting: components["schemas"]["Cat"] | components["schemas"]["Dog"] | components["schemas"]["Lizard"];
104+
Beast: components["schemas"]["Cat"] | components["schemas"]["Dog"] | components["schemas"]["Lizard"];
68105
};
69106
responses: never;
70107
parameters: never;

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

+2-12
Original file line numberDiff line numberDiff line change
@@ -258,20 +258,12 @@ describe("composition", () => {
258258
"discriminator > oneOf",
259259
{
260260
given: {
261-
type: "object",
262-
required: ["name"],
263-
properties: {
264-
name: { type: "string" },
265-
},
266261
oneOf: [
267262
{ $ref: "#/components/schemas/Cat" },
268263
{ $ref: "#/components/schemas/Dog" },
269264
],
270265
},
271-
want: `{
272-
petType: "Pet";
273-
name: string;
274-
} & (Omit<components["schemas"]["Cat"], "petType"> | Omit<components["schemas"]["Dog"], "petType">)`,
266+
want: `components["schemas"]["Cat"] | components["schemas"]["Dog"]`,
275267
options: {
276268
path: "#/components/schemas/Pet",
277269
ctx: {
@@ -313,9 +305,7 @@ describe("composition", () => {
313305
given: {
314306
oneOf: [{ $ref: "#/components/schemas/parent" }, { type: "null" }],
315307
},
316-
want: `{
317-
operation: "schema-object";
318-
} & (Omit<components["schemas"]["parent"], "operation"> | null)`,
308+
want: `components["schemas"]["parent"] | null`,
319309
options: {
320310
...DEFAULT_OPTIONS,
321311
ctx: {

0 commit comments

Comments
 (0)