@@ -88,6 +88,9 @@ public class OpenAPINormalizer {
88
88
// when set to true, boolean enum will be converted to just boolean
89
89
final String SIMPLIFY_BOOLEAN_ENUM = "SIMPLIFY_BOOLEAN_ENUM" ;
90
90
91
+ // when set to true, oneOf/anyOf with enum sub-schemas containing single values will be converted to a single enum
92
+ final String SIMPLIFY_ONEOF_ANYOF_ENUM = "SIMPLIFY_ONEOF_ANYOF_ENUM" ;
93
+
91
94
// when set to a string value, tags in all operations will be reset to the string value provided
92
95
final String SET_TAGS_FOR_ALL_OPERATIONS = "SET_TAGS_FOR_ALL_OPERATIONS" ;
93
96
String setTagsForAllOperations ;
@@ -205,11 +208,12 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
205
208
ruleNames .add (FILTER );
206
209
ruleNames .add (SET_CONTAINER_TO_NULLABLE );
207
210
ruleNames .add (SET_PRIMITIVE_TYPES_TO_NULLABLE );
208
-
211
+ ruleNames . add ( SIMPLIFY_ONEOF_ANYOF_ENUM );
209
212
210
213
// rules that are default to true
211
214
rules .put (SIMPLIFY_ONEOF_ANYOF , true );
212
215
rules .put (SIMPLIFY_BOOLEAN_ENUM , true );
216
+ rules .put (SIMPLIFY_ONEOF_ANYOF_ENUM , true );
213
217
214
218
processRules (inputRules );
215
219
@@ -972,6 +976,8 @@ protected Schema normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
972
976
// Remove duplicate oneOf entries
973
977
ModelUtils .deduplicateOneOfSchema (schema );
974
978
979
+ schema = processSimplifyOneOfEnum (schema );
980
+
975
981
// simplify first as the schema may no longer be a oneOf after processing the rule below
976
982
schema = processSimplifyOneOf (schema );
977
983
@@ -1000,6 +1006,11 @@ protected Schema normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
1000
1006
}
1001
1007
1002
1008
protected Schema normalizeAnyOf (Schema schema , Set <Schema > visitedSchemas ) {
1009
+ //transform anyOf into enums if needed
1010
+ schema = processSimplifyAnyOfEnum (schema );
1011
+ if (schema .getAnyOf () == null ) {
1012
+ return schema ;
1013
+ }
1003
1014
for (int i = 0 ; i < schema .getAnyOf ().size (); i ++) {
1004
1015
// normalize anyOf sub schemas one by one
1005
1016
Object item = schema .getAnyOf ().get (i );
@@ -1275,6 +1286,161 @@ protected Schema processSimplifyAnyOfStringAndEnumString(Schema schema) {
1275
1286
}
1276
1287
1277
1288
1289
+ /**
1290
+ * If the schema is anyOf and all sub-schemas are enums (with one or more values),
1291
+ * then simplify it to a single enum schema containing all the values.
1292
+ *
1293
+ * @param schema Schema
1294
+ * @return Schema
1295
+ */
1296
+ protected Schema processSimplifyAnyOfEnum (Schema schema ) {
1297
+ if (!getRule (SIMPLIFY_ONEOF_ANYOF_ENUM )) {
1298
+ return schema ;
1299
+ }
1300
+
1301
+ if (schema .getAnyOf () == null || schema .getAnyOf ().isEmpty ()) {
1302
+ return schema ;
1303
+ }
1304
+ if (schema .getOneOf () != null && !schema .getOneOf ().isEmpty () ||
1305
+ schema .getAllOf () != null && !schema .getAllOf ().isEmpty () ||
1306
+ schema .getNot () != null ) {
1307
+ //only convert to enum if anyOf is the only composition
1308
+ return schema ;
1309
+ }
1310
+
1311
+ return simplifyComposedSchemaWithEnums (schema , schema .getAnyOf (), "anyOf" );
1312
+ }
1313
+
1314
+ /**
1315
+ * If the schema is oneOf and all sub-schemas are enums (with one or more values),
1316
+ * then simplify it to a single enum schema containing all the values.
1317
+ *
1318
+ * @param schema Schema
1319
+ * @return Schema
1320
+ */
1321
+ protected Schema processSimplifyOneOfEnum (Schema schema ) {
1322
+ if (!getRule (SIMPLIFY_ONEOF_ANYOF_ENUM )) {
1323
+ return schema ;
1324
+ }
1325
+
1326
+ if (schema .getOneOf () == null || schema .getOneOf ().isEmpty ()) {
1327
+ return schema ;
1328
+ }
1329
+ if (schema .getAnyOf () != null && !schema .getAnyOf ().isEmpty () ||
1330
+ schema .getAllOf () != null && !schema .getAllOf ().isEmpty () ||
1331
+ schema .getNot () != null ) {
1332
+ //only convert to enum if oneOf is the only composition
1333
+ return schema ;
1334
+ }
1335
+
1336
+ return simplifyComposedSchemaWithEnums (schema , schema .getOneOf (), "oneOf" );
1337
+ }
1338
+
1339
+ /**
1340
+ * Simplifies a composed schema (oneOf/anyOf) where all sub-schemas are enums
1341
+ * to a single enum schema containing all the values.
1342
+ *
1343
+ * @param schema Schema to modify
1344
+ * @param subSchemas List of sub-schemas to check
1345
+ * @param schemaType Type of composed schema ("oneOf" or "anyOf")
1346
+ * @return Simplified schema
1347
+ */
1348
+ protected Schema simplifyComposedSchemaWithEnums (Schema schema , List <Object > subSchemas , String composedType ) {
1349
+ Map <Object , String > enumValues = new LinkedHashMap <>();
1350
+
1351
+ if (schema .getTypes () != null && schema .getTypes ().size () > 1 ) {
1352
+ // we cannot handle enums with multiple types
1353
+ return schema ;
1354
+ }
1355
+
1356
+ if (subSchemas .size () < 2 ) {
1357
+ //do not process if there's less than 2 sub-schemas. It will be normalized later, and this prevents
1358
+ //named enum schemas from being converted to inline enum schemas
1359
+ return schema ;
1360
+ }
1361
+ String schemaType = ModelUtils .getType (schema );
1362
+
1363
+ for (Object item : subSchemas ) {
1364
+ if (!(item instanceof Schema )) {
1365
+ return schema ;
1366
+ }
1367
+
1368
+ Schema subSchema = ModelUtils .getReferencedSchema (openAPI , (Schema ) item );
1369
+
1370
+ // Check if this sub-schema has an enum (with one or more values)
1371
+ if (subSchema .getEnum () == null || subSchema .getEnum ().isEmpty ()) {
1372
+ return schema ;
1373
+ }
1374
+
1375
+ // Ensure all sub-schemas have the same type (if type is specified)
1376
+ if (subSchema .getTypes () != null && subSchema .getTypes ().size () > 1 ) {
1377
+ // we cannot handle enums with multiple types
1378
+ return schema ;
1379
+ }
1380
+ String subSchemaType = ModelUtils .getType (subSchema );
1381
+ if (subSchemaType != null ) {
1382
+ if (schemaType == null ) {
1383
+ schemaType = subSchemaType ;
1384
+ } else if (!schemaType .equals (subSchema .getType ())) {
1385
+ return schema ;
1386
+ }
1387
+ }
1388
+ // Add all enum values from this sub-schema to our collection
1389
+ if (subSchema .getEnum ().size () == 1 ) {
1390
+ String description = subSchema .getTitle () == null ? "" : subSchema .getTitle ();
1391
+ if (subSchema .getDescription () != null ) {
1392
+ if (!description .isEmpty ()) {
1393
+ description += " - " ;
1394
+ }
1395
+ description += subSchema .getDescription ();
1396
+ }
1397
+ enumValues .put (subSchema .getEnum ().get (0 ), description );
1398
+ } else {
1399
+ for (Object e : subSchema .getEnum ()) {
1400
+ enumValues .put (e , "" );
1401
+ }
1402
+ }
1403
+
1404
+ }
1405
+
1406
+ return createSimplifiedEnumSchema (schema , enumValues , schemaType , composedType );
1407
+ }
1408
+
1409
+
1410
+ /**
1411
+ * Creates a simplified enum schema from collected enum values.
1412
+ *
1413
+ * @param originalSchema Original schema to modify
1414
+ * @param enumValues Collected enum values
1415
+ * @param schemaType Consistent type across sub-schemas
1416
+ * @param composedType Type of composed schema being simplified
1417
+ * @return Simplified enum schema
1418
+ */
1419
+ protected Schema createSimplifiedEnumSchema (Schema originalSchema , Map <Object , String > enumValues , String schemaType , String composedType ) {
1420
+ // Clear the composed schema type
1421
+ if ("oneOf" .equals (composedType )) {
1422
+ originalSchema .setOneOf (null );
1423
+ } else if ("anyOf" .equals (composedType )) {
1424
+ originalSchema .setAnyOf (null );
1425
+ }
1426
+
1427
+ if (ModelUtils .getType (originalSchema ) == null && schemaType != null ) {
1428
+ //if type was specified in subschemas, keep it in the main schema
1429
+ ModelUtils .setType (originalSchema , schemaType );
1430
+ }
1431
+
1432
+ originalSchema .setEnum (new ArrayList <>(enumValues .keySet ()));
1433
+ if (enumValues .values ().stream ().anyMatch (e -> !e .isEmpty ())) {
1434
+ //set x-enum-descriptions only if there's at least one non-empty description
1435
+ originalSchema .addExtension ("x-enum-descriptions" , new ArrayList <>(enumValues .values ()));
1436
+ }
1437
+
1438
+ LOGGER .debug ("Simplified {} with enum sub-schemas to single enum: {}" , composedType , originalSchema );
1439
+
1440
+ return originalSchema ;
1441
+ }
1442
+
1443
+
1278
1444
/**
1279
1445
* If the schema is oneOf and the sub-schemas is null, set `nullable: true`
1280
1446
* instead.
0 commit comments