@@ -171,14 +171,24 @@ export function resolveRef<T>(
171
171
return node ;
172
172
}
173
173
174
- function createDiscriminatorEnum ( value : string ) : SchemaObject {
174
+ function createDiscriminatorEnum (
175
+ values : string [ ] ,
176
+ prevSchema ?: SchemaObject ,
177
+ ) : SchemaObject {
175
178
return {
176
179
type : "string" ,
177
- enum : [ value ] ,
178
- description : `discriminator enum property added by openapi-typescript` ,
180
+ enum : values ,
181
+ description : prevSchema ?. description
182
+ ? `${ prevSchema . description } (enum property replaced by openapi-typescript)`
183
+ : `discriminator enum property added by openapi-typescript` ,
179
184
} ;
180
185
}
181
186
187
+ type InternalDiscriminatorMapping = Record <
188
+ string ,
189
+ { inferred ?: string ; defined ?: string [ ] }
190
+ > ;
191
+
182
192
/** Return a key–value map of discriminator objects found in a schema */
183
193
export function scanDiscriminators (
184
194
schema : OpenAPI3 ,
@@ -197,7 +207,7 @@ export function scanDiscriminators(
197
207
return ;
198
208
}
199
209
200
- // add to discriminators object for later usage
210
+ // collect discriminator object for later usage
201
211
const ref = createRef ( path ) ;
202
212
203
213
objects [ ref ] = discriminator ;
@@ -209,16 +219,20 @@ export function scanDiscriminators(
209
219
}
210
220
211
221
const oneOf : ( SchemaObject | ReferenceObject ) [ ] = obj . oneOf ;
212
- const mapping : Record < string , string > = { } ;
222
+ const mapping : InternalDiscriminatorMapping = { } ;
213
223
214
224
// the mapping can be inferred from the oneOf refs next to the discriminator object
215
225
for ( const item of oneOf ) {
216
226
if ( "$ref" in item ) {
217
- // the name of the schema is the infered discriminator enum value
227
+ // the name of the schema is the inferred discriminator enum value
218
228
const value = item . $ref . split ( "/" ) . pop ( ) ;
219
229
220
230
if ( value ) {
221
- mapping [ item . $ref ] = value ;
231
+ if ( ! mapping [ item . $ref ] ) {
232
+ mapping [ item . $ref ] = { inferred : value } ;
233
+ } else {
234
+ mapping [ item . $ref ] . inferred = value ;
235
+ }
222
236
}
223
237
}
224
238
}
@@ -227,26 +241,44 @@ export function scanDiscriminators(
227
241
if ( discriminator . mapping ) {
228
242
for ( const mappedValue in discriminator . mapping ) {
229
243
const mappedRef = discriminator . mapping [ mappedValue ] ;
244
+ if ( ! mappedRef ) {
245
+ continue ;
246
+ }
247
+
248
+ if ( ! mapping [ mappedRef ] ?. defined ) {
249
+ // this overrides inferred values, but we don't need them anymore as soon as we have a defined value
250
+ mapping [ mappedRef ] = { defined : [ ] } ;
251
+ }
230
252
231
- mapping [ mappedRef ] = mappedValue ;
253
+ mapping [ mappedRef ] . defined ?. push ( mappedValue ) ;
232
254
}
233
255
}
234
256
235
- for ( const [ mappedRef , mappedValue ] of Object . entries ( mapping ) ) {
257
+ for ( const [ mappedRef , { inferred , defined } ] of Object . entries ( mapping ) ) {
236
258
if ( refsHandled . includes ( mappedRef ) ) {
237
259
continue ;
238
260
}
239
261
262
+ if ( ! inferred && ! defined ) {
263
+ continue ;
264
+ }
265
+
266
+ // prefer defined values over automatically inferred ones
267
+ // the inferred enum values from the schema might not represent the actual enum values of the discriminator,
268
+ // so if we have defined values, use them instead
269
+ const mappedValues = defined ?? [ inferred ! ] ;
240
270
const resolvedSchema = resolveRef < SchemaObject > ( schema , mappedRef , {
241
271
silent : options . silent ?? false ,
242
272
} ) ;
273
+
243
274
if ( resolvedSchema ?. allOf ) {
244
275
// if the schema is an allOf, we can append a new schema object to the allOf array
245
276
resolvedSchema . allOf . push ( {
246
277
type : "object" ,
278
+ // discriminator enum properties always need to be required
247
279
required : [ discriminator . propertyName ] ,
248
280
properties : {
249
- [ discriminator . propertyName ] : createDiscriminatorEnum ( mappedValue ) ,
281
+ [ discriminator . propertyName ] : createDiscriminatorEnum ( mappedValues ) ,
250
282
} ,
251
283
} ) ;
252
284
@@ -256,11 +288,12 @@ export function scanDiscriminators(
256
288
"type" in resolvedSchema &&
257
289
resolvedSchema . type === "object"
258
290
) {
259
- // if the schema is an object, we can add/replace the discriminator enum to it
291
+ // if the schema is an object, we can apply the discriminator enums to its properties
260
292
if ( ! resolvedSchema . properties ) {
261
293
resolvedSchema . properties = { } ;
262
294
}
263
295
296
+ // discriminator enum properties always need to be required
264
297
if ( ! resolvedSchema . required ) {
265
298
resolvedSchema . required = [ discriminator . propertyName ] ;
266
299
} else if (
@@ -269,13 +302,19 @@ export function scanDiscriminators(
269
302
resolvedSchema . required . push ( discriminator . propertyName ) ;
270
303
}
271
304
305
+ // add/replace the discriminator enum property
272
306
resolvedSchema . properties [ discriminator . propertyName ] =
273
- createDiscriminatorEnum ( mappedValue ) ;
307
+ createDiscriminatorEnum (
308
+ mappedValues ,
309
+ resolvedSchema . properties [ discriminator . propertyName ] ,
310
+ ) ;
274
311
275
312
refsHandled . push ( mappedRef ) ;
276
313
} else {
277
314
warn (
278
- `Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${ mappedRef } => ${ mappedValue } (Discriminator: ${ ref } )` ,
315
+ `Discriminator mapping has an invalid schema (neither an object schema nor an allOf array): ${ mappedRef } => ${ mappedValues . join (
316
+ ", " ,
317
+ ) } (Discriminator: ${ ref } )`,
279
318
options . silent ,
280
319
) ;
281
320
continue ;
0 commit comments