Skip to content

Commit 9d35c09

Browse files
fix(openapi): handle discriminator mapping without oneOf in both conversion paths (#11406)
* fix: handle discriminator mapping without oneOf in OpenAPI->IR direct path Co-Authored-By: Sandeep Dinesh <[email protected]> * fix: handle discriminator mapping without oneOf in OpenAPI->Fern conversion path Co-Authored-By: Sandeep Dinesh <[email protected]> * chore: bump CLI version to 3.33.0 Co-Authored-By: Sandeep Dinesh <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 266a520 commit 9d35c09

File tree

8 files changed

+716
-0
lines changed

8 files changed

+716
-0
lines changed

packages/cli/api-importers/openapi/openapi-ir-parser/src/schema/convertSchemas.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,36 @@ export function convertSchemaObject(
11231123
});
11241124
}
11251125

1126+
// handle discriminator.mapping without oneOf/anyOf
1127+
// Some OpenAPI specs define discriminated unions using discriminator.mapping
1128+
// without explicit oneOf/anyOf. The convertDiscriminatedOneOf function will
1129+
// use the discriminator.mapping to build the union subtypes.
1130+
if (
1131+
schema.discriminator?.mapping != null &&
1132+
Object.keys(schema.discriminator.mapping).length > 0 &&
1133+
schema.oneOf == null &&
1134+
schema.anyOf == null
1135+
) {
1136+
return convertDiscriminatedOneOf({
1137+
nameOverride,
1138+
generatedName,
1139+
title,
1140+
breadcrumbs,
1141+
description,
1142+
availability,
1143+
discriminator: schema.discriminator,
1144+
properties: schema.properties ?? {},
1145+
required: schema.required,
1146+
wrapAsOptional,
1147+
wrapAsNullable,
1148+
context,
1149+
namespace,
1150+
groupName,
1151+
encoding,
1152+
source
1153+
});
1154+
}
1155+
11261156
// handle objects
11271157
if (schema.allOf != null || schema.properties != null) {
11281158
const filteredAllOfs: (OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject)[] = [];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
{
2+
"title": "Discriminator Base Class Test",
3+
"servers": [],
4+
"websocketServers": [],
5+
"tags": {
6+
"tagsById": {}
7+
},
8+
"hasEndpointsMarkedInternal": false,
9+
"endpoints": [
10+
{
11+
"audiences": [],
12+
"operationId": "createForm",
13+
"tags": [],
14+
"pathParameters": [],
15+
"queryParameters": [],
16+
"headers": [],
17+
"generatedRequestName": "CreateFormRequest",
18+
"request": {
19+
"schema": {
20+
"allOf": [],
21+
"properties": [
22+
{
23+
"conflict": {},
24+
"generatedName": "createFormRequestFormFields",
25+
"key": "form_fields",
26+
"schema": {
27+
"generatedName": "CreateFormRequestFormFields",
28+
"description": "Array of form fields with different types",
29+
"value": {
30+
"description": "Array of form fields with different types",
31+
"value": {
32+
"generatedName": "CreateFormRequestFormFieldsItem",
33+
"schema": "FormFieldBase",
34+
"source": {
35+
"file": "../openapi.yml",
36+
"type": "openapi"
37+
},
38+
"type": "reference"
39+
},
40+
"generatedName": "CreateFormRequestFormFields",
41+
"groupName": [],
42+
"type": "array"
43+
},
44+
"groupName": [],
45+
"type": "optional"
46+
},
47+
"audiences": []
48+
}
49+
],
50+
"allOfPropertyConflicts": [],
51+
"generatedName": "CreateFormRequest",
52+
"groupName": [],
53+
"additionalProperties": false,
54+
"source": {
55+
"file": "../openapi.yml",
56+
"type": "openapi"
57+
},
58+
"type": "object"
59+
},
60+
"contentType": "application/json",
61+
"fullExamples": [],
62+
"additionalProperties": false,
63+
"source": {
64+
"file": "../openapi.yml",
65+
"type": "openapi"
66+
},
67+
"type": "json"
68+
},
69+
"errors": {},
70+
"servers": [],
71+
"authed": false,
72+
"method": "POST",
73+
"path": "/form",
74+
"examples": [
75+
{
76+
"pathParameters": [],
77+
"queryParameters": [],
78+
"headers": [],
79+
"request": {
80+
"properties": {},
81+
"type": "object"
82+
},
83+
"codeSamples": [],
84+
"type": "full"
85+
}
86+
],
87+
"source": {
88+
"file": "../openapi.yml",
89+
"type": "openapi"
90+
}
91+
}
92+
],
93+
"webhooks": [],
94+
"channels": {},
95+
"groupedSchemas": {
96+
"rootSchemas": {
97+
"FormFieldBase": {
98+
"value": {
99+
"commonProperties": [
100+
{
101+
"key": "name",
102+
"schema": {
103+
"description": "Display name for the field",
104+
"schema": {
105+
"type": "string"
106+
},
107+
"generatedName": "FormFieldBaseName",
108+
"groupName": [],
109+
"type": "primitive"
110+
}
111+
},
112+
{
113+
"key": "x",
114+
"schema": {
115+
"description": "X coordinate",
116+
"schema": {
117+
"type": "int"
118+
},
119+
"generatedName": "FormFieldBaseX",
120+
"groupName": [],
121+
"type": "primitive"
122+
}
123+
},
124+
{
125+
"key": "y",
126+
"schema": {
127+
"description": "Y coordinate",
128+
"schema": {
129+
"type": "int"
130+
},
131+
"generatedName": "FormFieldBaseY",
132+
"groupName": [],
133+
"type": "primitive"
134+
}
135+
}
136+
],
137+
"description": "Base form field with discriminator",
138+
"discriminantProperty": "type",
139+
"generatedName": "FormFieldBase",
140+
"schemas": {
141+
"text": {
142+
"generatedName": "ComponentsSchemasFormFieldText",
143+
"schema": "FormFieldText",
144+
"source": {
145+
"file": "../openapi.yml",
146+
"type": "openapi"
147+
},
148+
"type": "reference"
149+
},
150+
"checkbox": {
151+
"generatedName": "ComponentsSchemasFormFieldCheckbox",
152+
"schema": "FormFieldCheckbox",
153+
"source": {
154+
"file": "../openapi.yml",
155+
"type": "openapi"
156+
},
157+
"type": "reference"
158+
},
159+
"signature": {
160+
"generatedName": "ComponentsSchemasFormFieldSignature",
161+
"schema": "FormFieldSignature",
162+
"source": {
163+
"file": "../openapi.yml",
164+
"type": "openapi"
165+
},
166+
"type": "reference"
167+
}
168+
},
169+
"groupName": [],
170+
"source": {
171+
"file": "../openapi.yml",
172+
"type": "openapi"
173+
},
174+
"type": "discriminated"
175+
},
176+
"type": "oneOf"
177+
},
178+
"FormFieldText": {
179+
"allOf": [],
180+
"properties": [
181+
{
182+
"conflict": {},
183+
"generatedName": "formFieldTextPlaceholder",
184+
"key": "placeholder",
185+
"schema": {
186+
"generatedName": "FormFieldTextPlaceholder",
187+
"description": "Placeholder text",
188+
"value": {
189+
"description": "Placeholder text",
190+
"schema": {
191+
"type": "string"
192+
},
193+
"generatedName": "FormFieldTextPlaceholder",
194+
"groupName": [],
195+
"type": "primitive"
196+
},
197+
"groupName": [],
198+
"type": "optional"
199+
},
200+
"audiences": []
201+
},
202+
{
203+
"conflict": {},
204+
"generatedName": "formFieldTextMaxLength",
205+
"key": "maxLength",
206+
"schema": {
207+
"generatedName": "FormFieldTextMaxLength",
208+
"description": "Maximum length",
209+
"value": {
210+
"description": "Maximum length",
211+
"schema": {
212+
"type": "int"
213+
},
214+
"generatedName": "FormFieldTextMaxLength",
215+
"groupName": [],
216+
"type": "primitive"
217+
},
218+
"groupName": [],
219+
"type": "optional"
220+
},
221+
"audiences": []
222+
}
223+
],
224+
"allOfPropertyConflicts": [],
225+
"description": "Text input field",
226+
"generatedName": "FormFieldText",
227+
"groupName": [],
228+
"additionalProperties": false,
229+
"source": {
230+
"file": "../openapi.yml",
231+
"type": "openapi"
232+
},
233+
"type": "object"
234+
},
235+
"FormFieldCheckbox": {
236+
"allOf": [],
237+
"properties": [
238+
{
239+
"conflict": {},
240+
"generatedName": "formFieldCheckboxChecked",
241+
"key": "checked",
242+
"schema": {
243+
"generatedName": "FormFieldCheckboxChecked",
244+
"description": "Default checked state",
245+
"value": {
246+
"description": "Default checked state",
247+
"schema": {
248+
"type": "boolean"
249+
},
250+
"generatedName": "FormFieldCheckboxChecked",
251+
"groupName": [],
252+
"type": "primitive"
253+
},
254+
"groupName": [],
255+
"type": "optional"
256+
},
257+
"audiences": []
258+
}
259+
],
260+
"allOfPropertyConflicts": [],
261+
"description": "Checkbox field",
262+
"generatedName": "FormFieldCheckbox",
263+
"groupName": [],
264+
"additionalProperties": false,
265+
"source": {
266+
"file": "../openapi.yml",
267+
"type": "openapi"
268+
},
269+
"type": "object"
270+
},
271+
"FormFieldSignature": {
272+
"allOf": [],
273+
"properties": [
274+
{
275+
"conflict": {},
276+
"generatedName": "formFieldSignatureRequired",
277+
"key": "required",
278+
"schema": {
279+
"generatedName": "FormFieldSignatureRequired",
280+
"description": "Whether signature is required",
281+
"value": {
282+
"description": "Whether signature is required",
283+
"schema": {
284+
"type": "boolean"
285+
},
286+
"generatedName": "FormFieldSignatureRequired",
287+
"groupName": [],
288+
"type": "primitive"
289+
},
290+
"groupName": [],
291+
"type": "optional"
292+
},
293+
"audiences": []
294+
}
295+
],
296+
"allOfPropertyConflicts": [],
297+
"description": "Signature field",
298+
"generatedName": "FormFieldSignature",
299+
"groupName": [],
300+
"additionalProperties": false,
301+
"source": {
302+
"file": "../openapi.yml",
303+
"type": "openapi"
304+
},
305+
"type": "object"
306+
}
307+
},
308+
"namespacedSchemas": {}
309+
},
310+
"variables": {},
311+
"nonRequestReferencedSchemas": {},
312+
"securitySchemes": {},
313+
"globalHeaders": [],
314+
"idempotencyHeaders": [],
315+
"groups": {}
316+
}

0 commit comments

Comments
 (0)