@@ -81,6 +81,10 @@ public class OpenAPINormalizer {
81
81
82
82
// when set to true, boolean enum will be converted to just boolean
83
83
final String SIMPLIFY_BOOLEAN_ENUM = "SIMPLIFY_BOOLEAN_ENUM" ;
84
+
85
+ // when set to true, oneOf with multiple enum schemas will be merged into a single enum schema
86
+ // even if one of them is an object
87
+ final String SIMPLIFY_ONEOF_ENUM = "SIMPLIFY_ONEOF_ENUM" ;
84
88
85
89
// when set to a string value, tags in all operations will be reset to the string value provided
86
90
final String SET_TAGS_FOR_ALL_OPERATIONS = "SET_TAGS_FOR_ALL_OPERATIONS" ;
@@ -159,6 +163,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
159
163
ruleNames .add (SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING );
160
164
ruleNames .add (SIMPLIFY_ONEOF_ANYOF );
161
165
ruleNames .add (SIMPLIFY_BOOLEAN_ENUM );
166
+ ruleNames .add (SIMPLIFY_ONEOF_ENUM );
162
167
ruleNames .add (KEEP_ONLY_FIRST_TAG_IN_OPERATION );
163
168
ruleNames .add (SET_TAGS_FOR_ALL_OPERATIONS );
164
169
ruleNames .add (SET_TAGS_TO_OPERATIONID );
@@ -176,6 +181,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
176
181
// rules that are default to true
177
182
rules .put (SIMPLIFY_ONEOF_ANYOF , true );
178
183
rules .put (SIMPLIFY_BOOLEAN_ENUM , true );
184
+ rules .put (SIMPLIFY_ONEOF_ENUM , true );
179
185
180
186
processRules (inputRules );
181
187
@@ -787,6 +793,9 @@ private Schema normalizeAllOfWithProperties(Schema schema, Set<Schema> visitedSc
787
793
private Schema normalizeOneOf (Schema schema , Set <Schema > visitedSchemas ) {
788
794
// simplify first as the schema may no longer be a oneOf after processing the rule below
789
795
schema = processSimplifyOneOf (schema );
796
+
797
+ // try to merge enum schemas
798
+ schema = processSimplifyOneOfEnum (schema , visitedSchemas );
790
799
791
800
// if it's still a oneOf, loop through the sub-schemas
792
801
if (schema .getOneOf () != null ) {
@@ -1304,6 +1313,113 @@ private void processSimplifyBooleanEnum(Schema schema) {
1304
1313
}
1305
1314
}
1306
1315
}
1316
+
1317
+ /**
1318
+ * If the schema is oneOf with multiple enum schemas, merge them into a single enum schema
1319
+ * even if one of them is an object.
1320
+ *
1321
+ * @param schema Schema
1322
+ * @param visitedSchemas a set of visited schemas
1323
+ * @return Schema
1324
+ */
1325
+ private Schema processSimplifyOneOfEnum (Schema schema , Set <Schema > visitedSchemas ) {
1326
+ if (!getRule (SIMPLIFY_ONEOF_ENUM )) {
1327
+ return schema ;
1328
+ }
1329
+
1330
+ List <Schema > oneOfSchemas = schema .getOneOf ();
1331
+ if (oneOfSchemas == null || oneOfSchemas .size () <= 1 ) {
1332
+ return schema ;
1333
+ }
1334
+
1335
+ // Check if all schemas are either objects or have enums
1336
+ boolean allEnumSchemas = true ;
1337
+ List <Object > allEnumValues = new ArrayList <>();
1338
+ StringSchema mergedSchema = null ;
1339
+
1340
+ for (Schema subSchema : oneOfSchemas ) {
1341
+ subSchema = ModelUtils .getReferencedSchema (openAPI , subSchema );
1342
+
1343
+ if (subSchema instanceof StringSchema && ((StringSchema )subSchema ).getEnum () != null ) {
1344
+ if (mergedSchema == null ) {
1345
+ // Use the first StringSchema as our template
1346
+ mergedSchema = new StringSchema ();
1347
+ mergedSchema .setDescription (schema .getDescription ());
1348
+ mergedSchema .setExample (schema .getExample ());
1349
+ mergedSchema .setExamples (schema .getExamples ());
1350
+ mergedSchema .setNullable (schema .getNullable ());
1351
+ mergedSchema .setDefault (schema .getDefault ());
1352
+ mergedSchema .setDeprecated (schema .getDeprecated ());
1353
+ }
1354
+ // Add all enum values from this schema
1355
+ allEnumValues .addAll (((StringSchema )subSchema ).getEnum ());
1356
+ } else if (ModelUtils .isObjectSchema (subSchema )) {
1357
+ // If it's an object, we'll consider it valid for merging
1358
+ // but we need to extract its type name as an enum value
1359
+
1360
+ // Get schema name or create a placeholder
1361
+ String objectEnumValue = determineObjectEnumName (subSchema );
1362
+ if (objectEnumValue != null ) {
1363
+ if (mergedSchema == null ) {
1364
+ mergedSchema = new StringSchema ();
1365
+ mergedSchema .setDescription (schema .getDescription ());
1366
+ mergedSchema .setExample (schema .getExample ());
1367
+ mergedSchema .setExamples (schema .getExamples ());
1368
+ mergedSchema .setNullable (schema .getNullable ());
1369
+ mergedSchema .setDefault (schema .getDefault ());
1370
+ mergedSchema .setDeprecated (schema .getDeprecated ());
1371
+ }
1372
+ allEnumValues .add (objectEnumValue );
1373
+ } else {
1374
+ // If we can't determine a name, we can't merge
1375
+ allEnumSchemas = false ;
1376
+ break ;
1377
+ }
1378
+ } else {
1379
+ // This schema is not an enum or object, can't merge
1380
+ allEnumSchemas = false ;
1381
+ break ;
1382
+ }
1383
+ }
1384
+
1385
+ if (allEnumSchemas && mergedSchema != null && !allEnumValues .isEmpty ()) {
1386
+ // Remove duplicates and convert to strings
1387
+ Set <String > uniqueEnumValues = new LinkedHashSet <>();
1388
+ for (Object value : allEnumValues ) {
1389
+ uniqueEnumValues .add (value .toString ());
1390
+ }
1391
+ mergedSchema .setEnum (new ArrayList <>(uniqueEnumValues ));
1392
+
1393
+ LOGGER .debug ("Merged {} oneOf enum schemas into a single enum schema with values: {}" ,
1394
+ oneOfSchemas .size (), uniqueEnumValues );
1395
+
1396
+ return mergedSchema ;
1397
+ }
1398
+
1399
+ return schema ;
1400
+ }
1401
+
1402
+ /**
1403
+ * Determines a meaningful enum value name for an object schema
1404
+ *
1405
+ * @param schema The object schema to determine a name for
1406
+ * @return A string representing the object name, or null if can't be determined
1407
+ */
1408
+ private String determineObjectEnumName (Schema schema ) {
1409
+ // Try to use title first
1410
+ if (schema .getTitle () != null ) {
1411
+ return schema .getTitle ();
1412
+ }
1413
+
1414
+ // Try to use type or $ref name
1415
+ if (schema .get$ref () != null ) {
1416
+ String ref = ModelUtils .getSimpleRef (schema .get$ref ());
1417
+ return ref ;
1418
+ }
1419
+
1420
+ // If no clear name, use a generic placeholder for an object
1421
+ return "object" ;
1422
+ }
1307
1423
1308
1424
/**
1309
1425
* If the schema is integer and the max value is invalid (out of bound)
0 commit comments