@@ -48,6 +48,17 @@ public class OpenAPINormalizer {
48
48
final String REF_AS_PARENT_IN_ALLOF = "REF_AS_PARENT_IN_ALLOF" ;
49
49
boolean enableRefAsParentInAllOf ;
50
50
51
+ // when set to true, complex composed schemas (a mix of oneOf/anyOf/anyOf and properties) with
52
+ // oneOf/anyOf containing only `required` and no properties (these are properties inter-dependency rules)
53
+ // are removed as most generators cannot handle such case at the moment
54
+ final String REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY = "REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY" ;
55
+ boolean removeAnyOfOneOfAndKeepPropertiesOnly ;
56
+
57
+ // when set to true, oneOf/anyOf with either string or enum string as sub schemas will be simplified
58
+ // to just string
59
+ final String SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING = "SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING" ;
60
+ boolean simplifyAnyOfStringAndEnumString ;
61
+
51
62
// ============= end of rules =============
52
63
53
64
/**
@@ -79,6 +90,14 @@ public void parseRules(Map<String, String> rules) {
79
90
if (enableAll || "true" .equalsIgnoreCase (rules .get (REF_AS_PARENT_IN_ALLOF ))) {
80
91
enableRefAsParentInAllOf = true ;
81
92
}
93
+
94
+ if (enableAll || "true" .equalsIgnoreCase (rules .get (REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY ))) {
95
+ removeAnyOfOneOfAndKeepPropertiesOnly = true ;
96
+ }
97
+
98
+ if (enableAll || "true" .equalsIgnoreCase (rules .get (SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING ))) {
99
+ simplifyAnyOfStringAndEnumString = true ;
100
+ }
82
101
}
83
102
84
103
/**
@@ -235,7 +254,8 @@ private void normalizeComponents() {
235
254
if (schema == null ) {
236
255
LOGGER .warn ("{} not fount found in openapi/components/schemas." , schemaName );
237
256
} else {
238
- normalizeSchema (schema , new HashSet <>());
257
+ Schema result = normalizeSchema (schema , new HashSet <>());
258
+ schemas .put (schemaName , result );
239
259
}
240
260
}
241
261
}
@@ -245,19 +265,20 @@ private void normalizeComponents() {
245
265
*
246
266
* @param schema Schema
247
267
* @param visitedSchemas a set of visited schemas
268
+ * @return Schema
248
269
*/
249
- public void normalizeSchema (Schema schema , Set <Schema > visitedSchemas ) {
270
+ public Schema normalizeSchema (Schema schema , Set <Schema > visitedSchemas ) {
250
271
if (schema == null ) {
251
- return ;
272
+ return schema ;
252
273
}
253
274
254
275
if (StringUtils .isNotEmpty (schema .get$ref ())) {
255
276
// not need to process $ref
256
- return ;
277
+ return schema ;
257
278
}
258
279
259
280
if ((visitedSchemas .contains (schema ))) {
260
- return ; // skip due to circular reference
281
+ return schema ; // skip due to circular reference
261
282
} else {
262
283
visitedSchemas .add (schema );
263
284
}
@@ -267,38 +288,47 @@ public void normalizeSchema(Schema schema, Set<Schema> visitedSchemas) {
267
288
} else if (schema .getAdditionalProperties () instanceof Schema ) { // map
268
289
normalizeSchema ((Schema ) schema .getAdditionalProperties (), visitedSchemas );
269
290
} else if (ModelUtils .isComposedSchema (schema )) {
270
- ComposedSchema m = (ComposedSchema ) schema ;
271
- if (m .getAllOf () != null && !m .getAllOf ().isEmpty ()) {
272
- normalizeAllOf (m , visitedSchemas );
291
+ ComposedSchema cs = (ComposedSchema ) schema ;
292
+
293
+ if (ModelUtils .isComplexComposedSchema (cs )) {
294
+ cs = (ComposedSchema ) normalizeComplexComposedSchema (cs , visitedSchemas );
295
+ }
296
+
297
+ if (cs .getAllOf () != null && !cs .getAllOf ().isEmpty ()) {
298
+ return normalizeAllOf (cs , visitedSchemas );
273
299
}
274
300
275
- if (m .getOneOf () != null && !m .getOneOf ().isEmpty ()) {
276
- normalizeOneOf (m , visitedSchemas );
301
+ if (cs .getOneOf () != null && !cs .getOneOf ().isEmpty ()) {
302
+ return normalizeOneOf (cs , visitedSchemas );
277
303
}
278
304
279
- if (m .getAnyOf () != null && !m .getAnyOf ().isEmpty ()) {
280
- normalizeAnyOf (m , visitedSchemas );
305
+ if (cs .getAnyOf () != null && !cs .getAnyOf ().isEmpty ()) {
306
+ return normalizeAnyOf (cs , visitedSchemas );
281
307
}
282
308
283
- if (m .getProperties () != null && !m .getProperties ().isEmpty ()) {
284
- normalizeProperties (m .getProperties (), visitedSchemas );
309
+ if (cs .getProperties () != null && !cs .getProperties ().isEmpty ()) {
310
+ normalizeProperties (cs .getProperties (), visitedSchemas );
285
311
}
286
312
287
- if (m .getAdditionalProperties () != null ) {
313
+ if (cs .getAdditionalProperties () != null ) {
288
314
// normalizeAdditionalProperties(m);
289
315
}
316
+
317
+ return cs ;
290
318
} else if (schema .getNot () != null ) {// not schema
291
319
normalizeSchema (schema .getNot (), visitedSchemas );
292
320
} else if (schema .getProperties () != null && !schema .getProperties ().isEmpty ()) {
293
321
normalizeProperties (schema .getProperties (), visitedSchemas );
294
322
} else if (schema instanceof Schema ) {
295
- normalizeNonComposedSchema (schema , visitedSchemas );
323
+ normalizeSchemaWithOnlyProperties (schema , visitedSchemas );
296
324
} else {
297
325
throw new RuntimeException ("Unknown schema type found in normalizer: " + schema );
298
326
}
327
+
328
+ return schema ;
299
329
}
300
330
301
- private void normalizeNonComposedSchema (Schema schema , Set <Schema > visitedSchemas ) {
331
+ private void normalizeSchemaWithOnlyProperties (Schema schema , Set <Schema > visitedSchemas ) {
302
332
// normalize non-composed schema (e.g. schema with only properties)
303
333
}
304
334
@@ -312,7 +342,7 @@ private void normalizeProperties(Map<String, Schema> properties, Set<Schema> vis
312
342
}
313
343
}
314
344
315
- private void normalizeAllOf (Schema schema , Set <Schema > visitedSchemas ) {
345
+ private Schema normalizeAllOf (Schema schema , Set <Schema > visitedSchemas ) {
316
346
for (Object item : schema .getAllOf ()) {
317
347
if (!(item instanceof Schema )) {
318
348
throw new RuntimeException ("Error! allOf schema is not of the type Schema: " + item );
@@ -322,34 +352,55 @@ private void normalizeAllOf(Schema schema, Set<Schema> visitedSchemas) {
322
352
}
323
353
// process rules here
324
354
processUseAllOfRefAsParent (schema );
355
+
356
+ return schema ;
325
357
}
326
358
327
- private void normalizeOneOf (Schema schema , Set <Schema > visitedSchemas ) {
328
- for (Object item : schema .getAllOf ()) {
359
+ private Schema normalizeOneOf (Schema schema , Set <Schema > visitedSchemas ) {
360
+ for (Object item : schema .getOneOf ()) {
329
361
if (!(item instanceof Schema )) {
330
362
throw new RuntimeException ("Error! allOf schema is not of the type Schema: " + item );
331
363
}
332
364
// normalize oenOf sub schemas one by one
333
365
normalizeSchema ((Schema ) item , visitedSchemas );
334
366
}
367
+
335
368
// process rules here
369
+ return schema ;
336
370
}
337
371
338
- private void normalizeAnyOf (Schema schema , Set <Schema > visitedSchemas ) {
339
- for (Object item : schema .getAllOf ()) {
372
+ private Schema normalizeAnyOf (Schema schema , Set <Schema > visitedSchemas ) {
373
+ for (Object item : schema .getAnyOf ()) {
340
374
if (!(item instanceof Schema )) {
341
375
throw new RuntimeException ("Error! allOf schema is not of the type Schema: " + item );
342
376
}
343
377
// normalize anyOf sub schemas one by one
344
378
normalizeSchema ((Schema ) item , visitedSchemas );
345
379
}
380
+
346
381
// process rules here
382
+
383
+ // last rule to process as the schema may become String schema (not "anyOf") after the completion
384
+ return processSimplifyAnyOfStringAndEnumString (schema );
385
+ }
386
+
387
+ private Schema normalizeComplexComposedSchema (Schema schema , Set <Schema > visitedSchemas ) {
388
+
389
+ processRemoveAnyOfOneOfAndKeepPropertiesOnly (schema );
390
+
391
+ return schema ;
347
392
}
348
393
349
394
// ===================== a list of rules =====================
350
395
// all rules (fuctions) start with the word "process"
396
+
397
+ /**
398
+ * Child schemas in `allOf` is considered a parent if it's a `$ref` (instead of inline schema).
399
+ *
400
+ * @param schema Schema
401
+ */
351
402
private void processUseAllOfRefAsParent (Schema schema ) {
352
- if (!enableRefAsParentInAllOf ) {
403
+ if (!enableRefAsParentInAllOf && ! enableAll ) {
353
404
return ;
354
405
}
355
406
@@ -380,5 +431,65 @@ private void processUseAllOfRefAsParent(Schema schema) {
380
431
}
381
432
}
382
433
}
434
+
435
+ /**
436
+ * If the schema contains anyOf/oneOf and properties, remove oneOf/anyOf as these serve as rules to
437
+ * ensure inter-dependency between properties. It's a workaround as such validation is not supported at the moment.
438
+ *
439
+ * @param schema Schema
440
+ */
441
+ private void processRemoveAnyOfOneOfAndKeepPropertiesOnly (Schema schema ) {
442
+
443
+ if (!removeAnyOfOneOfAndKeepPropertiesOnly && !enableAll ) {
444
+ return ;
445
+ }
446
+
447
+ if (((schema .getOneOf () != null && !schema .getOneOf ().isEmpty ())
448
+ || (schema .getAnyOf () != null && !schema .getAnyOf ().isEmpty ())) // has anyOf or oneOf
449
+ && (schema .getProperties () != null && !schema .getProperties ().isEmpty ()) // has properties
450
+ && schema .getAllOf () == null ) { // not allOf
451
+ // clear oneOf, anyOf
452
+ schema .setOneOf (null );
453
+ schema .setAnyOf (null );
454
+ }
455
+ }
456
+
457
+ /**
458
+ * If the schema is anyOf and the sub-schemas are either string or enum of string,
459
+ * then simply it to just string as many generators do not yet support anyOf.
460
+ *
461
+ * @param schema Schema
462
+ * @return Schema
463
+ */
464
+ private Schema processSimplifyAnyOfStringAndEnumString (Schema schema ) {
465
+ if (!simplifyAnyOfStringAndEnumString && !enableAll ) {
466
+ return schema ;
467
+ }
468
+
469
+ Schema s0 = null , s1 = null ;
470
+ if (schema .getAnyOf ().size () == 2 ) {
471
+ s0 = ModelUtils .unaliasSchema (openAPI , (Schema ) schema .getAnyOf ().get (0 ));
472
+ s1 = ModelUtils .unaliasSchema (openAPI , (Schema ) schema .getAnyOf ().get (1 ));
473
+ } else {
474
+ return schema ;
475
+ }
476
+
477
+ s0 = ModelUtils .getReferencedSchema (openAPI , s0 );
478
+ s1 = ModelUtils .getReferencedSchema (openAPI , s1 );
479
+
480
+ // find the string schema (not enum)
481
+ if (s0 instanceof StringSchema && s1 instanceof StringSchema ) {
482
+ if (((StringSchema ) s0 ).getEnum () != null ) { // s0 is enum, s1 is string
483
+ return (StringSchema ) s1 ;
484
+ } else if (((StringSchema ) s1 ).getEnum () != null ) { // s1 is enum, s0 is string
485
+ return (StringSchema ) s0 ;
486
+ } else { // both are string
487
+ return schema ;
488
+ }
489
+ } else {
490
+ return schema ;
491
+ }
492
+ }
493
+
383
494
// ===================== end of rules =====================
384
495
}
0 commit comments