@@ -81,6 +81,10 @@ public class OpenAPINormalizer {
8181
8282 // when set to true, boolean enum will be converted to just boolean
8383 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" ;
8488
8589 // when set to a string value, tags in all operations will be reset to the string value provided
8690 final String SET_TAGS_FOR_ALL_OPERATIONS = "SET_TAGS_FOR_ALL_OPERATIONS" ;
@@ -159,6 +163,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
159163 ruleNames .add (SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING );
160164 ruleNames .add (SIMPLIFY_ONEOF_ANYOF );
161165 ruleNames .add (SIMPLIFY_BOOLEAN_ENUM );
166+ ruleNames .add (SIMPLIFY_ONEOF_ENUM );
162167 ruleNames .add (KEEP_ONLY_FIRST_TAG_IN_OPERATION );
163168 ruleNames .add (SET_TAGS_FOR_ALL_OPERATIONS );
164169 ruleNames .add (SET_TAGS_TO_OPERATIONID );
@@ -176,6 +181,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
176181 // rules that are default to true
177182 rules .put (SIMPLIFY_ONEOF_ANYOF , true );
178183 rules .put (SIMPLIFY_BOOLEAN_ENUM , true );
184+ rules .put (SIMPLIFY_ONEOF_ENUM , true );
179185
180186 processRules (inputRules );
181187
@@ -787,6 +793,9 @@ private Schema normalizeAllOfWithProperties(Schema schema, Set<Schema> visitedSc
787793 private Schema normalizeOneOf (Schema schema , Set <Schema > visitedSchemas ) {
788794 // simplify first as the schema may no longer be a oneOf after processing the rule below
789795 schema = processSimplifyOneOf (schema );
796+
797+ // try to merge enum schemas
798+ schema = processSimplifyOneOfEnum (schema , visitedSchemas );
790799
791800 // if it's still a oneOf, loop through the sub-schemas
792801 if (schema .getOneOf () != null ) {
@@ -1304,6 +1313,113 @@ private void processSimplifyBooleanEnum(Schema schema) {
13041313 }
13051314 }
13061315 }
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+ }
13071423
13081424 /**
13091425 * If the schema is integer and the max value is invalid (out of bound)
0 commit comments