Skip to content

Commit 1545db1

Browse files
committed
Fix: Handle nullable oneOf schemas correctly in normalizer
1 parent 0820bce commit 1545db1

2 files changed

Lines changed: 109 additions & 16 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -963,36 +963,75 @@ protected Schema normalizeAllOfWithProperties(Schema schema, Set<Schema> visited
963963
return schema;
964964
}
965965

966+
protected Schema simplifyNullableOneOf(Schema schema) {
967+
968+
if (schema.getOneOf() == null || schema.getOneOf().size() != 2) {
969+
// Not the pattern we are looking for, so do nothing.
970+
return schema;
971+
}
972+
973+
List<Schema> subSchemas = new ArrayList<>(schema.getOneOf());
974+
Schema nullSchema = null;
975+
Schema mainSchema = null;
976+
977+
// Identify the null schema and the main schema
978+
if (ModelUtils.isNullTypeSchema(openAPI, subSchemas.get(0))) {
979+
nullSchema = subSchemas.get(0);
980+
mainSchema = subSchemas.get(1);
981+
} else if (ModelUtils.isNullTypeSchema(openAPI, subSchemas.get(1))) {
982+
nullSchema = subSchemas.get(1);
983+
mainSchema = subSchemas.get(0);
984+
} else {
985+
// Pattern does not contain a null type, so do nothing.
986+
return schema;
987+
}
988+
989+
// Clone the main schema to avoid modifying the original component
990+
Schema result = ModelUtils.cloneSchema(mainSchema);
991+
992+
// Set nullable to true on the result
993+
result.setNullable(true);
994+
995+
// Copy metadata from the original container schema to the result
996+
ModelUtils.copyMetadata(schema, result);
997+
998+
LOGGER.debug("Simplified nullable oneOf schema.");
999+
return result;
1000+
}
1001+
9661002
protected Schema normalizeOneOf(Schema schema, Set<Schema> visitedSchemas) {
9671003
// Remove duplicate oneOf entries
9681004
ModelUtils.deduplicateOneOfSchema(schema);
9691005

970-
schema = processSimplifyOneOfEnum(schema);
971-
972-
// simplify first as the schema may no longer be a oneOf after processing the rule below
973-
schema = processSimplifyOneOf(schema);
1006+
// Try to simplify oneOf with a nullable type first
1007+
schema = simplifyNullableOneOf(schema);
9741008

975-
// if it's still a oneOf, loop through the sub-schemas
1009+
// if it's still a oneOf after simplification, continue with other rules
9761010
if (schema.getOneOf() != null) {
977-
for (int i = 0; i < schema.getOneOf().size(); i++) {
978-
// normalize oneOf sub schemas one by one
979-
Object item = schema.getOneOf().get(i);
980-
1011+
schema = processSimplifyOneOfEnum(schema);
1012+
// simplify first as the schema may no longer be a oneOf after processing the rule below
1013+
schema = processSimplifyOneOf(schema);
1014+
1015+
if (schema.getOneOf() != null) {
1016+
for (int i = 0; i < schema.getOneOf().size(); i++) {
1017+
// normalize oneOf sub schemas one by one
1018+
Object item = schema.getOneOf().get(i);
1019+
}
9811020
if (item == null) {
982-
continue;
1021+
continue;
9831022
}
9841023
if (!(item instanceof Schema)) {
985-
throw new RuntimeException("Error! oneOf schema is not of the type Schema: " + item);
986-
}
987-
988-
// update sub-schema with the updated schema
1024+
throw new RuntimeException("Error! oneOf schema is not of the type Schema: " + item);
9891025
schema.getOneOf().set(i, normalizeSchema((Schema) item, visitedSchemas));
9901026
}
9911027
} else {
992-
// normalize it as it's no longer an oneOf
1028+
// normalize it as it's no longer a oneOf
9931029
schema = normalizeSchema(schema, visitedSchemas);
9941030
}
995-
1031+
} else {
1032+
// It was simplified and is no longer a oneOf, so normalize it as a simple schema
1033+
return normalizeSchema(schema, visitedSchemas);
1034+
}
9961035
return schema;
9971036
}
9981037

modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,60 @@ public void testRemoveXInternalFromInlineProperties() {
12511251
assertNotNull(inlinePropertyAfter.getProperties().get("nestedNumber"));
12521252
}
12531253

1254+
@Test
1255+
public void testSimplifyNullableOneOf() {
1256+
// 1. Define the OpenAPI spec string that contains the bug pattern
1257+
final String yaml =
1258+
"openapi: 3.0.0\n" +
1259+
"info:\n" +
1260+
" version: 1.0.0\n" +
1261+
" title: API\n" +
1262+
"paths:\n" +
1263+
" /test:\n" +
1264+
" get:\n" +
1265+
" responses:\n" +
1266+
" '200':\n" +
1267+
" description: OK\n" +
1268+
" content:\n" +
1269+
" application/json:\n" +
1270+
" schema:\n" +
1271+
" $ref: '#/components/schemas/Recipe'\n" +
1272+
"components:\n" +
1273+
" schemas:\n" +
1274+
" Category:\n" +
1275+
" type: object\n" +
1276+
" properties:\n" +
1277+
" name:\n" +
1278+
" type: string\n" +
1279+
" Recipe:\n" +
1280+
" type: object\n" +
1281+
" properties:\n" +
1282+
" category:\n" + // This is the property with the nullable oneOf
1283+
" description: 'This should become nullable'\n" +
1284+
" oneOf:\n" +
1285+
" - $ref: '#/components/schemas/Category'\n" +
1286+
" - type: 'null'\n";
1287+
1288+
// 2. Load the spec and run the normalizer (with default rules)
1289+
final OpenAPI openAPI = TestUtils.parseSpec(yaml);
1290+
new OpenAPINormalizer(openAPI, Collections.emptyMap()).normalize();
1291+
1292+
// 3. Get the specific schema property that should have been fixed
1293+
Schema recipeSchema = openAPI.getComponents().getSchemas().get("Recipe");
1294+
Schema categoryProperty = (Schema) recipeSchema.getProperties().get("category");
1295+
1296+
// 4. Assert that the fix worked as expected
1297+
assertNotNull(categoryProperty, "The category property should not be null.");
1298+
1299+
// Key Assertions:
1300+
assertTrue(categoryProperty.getNullable(), "The property should be marked as nullable.");
1301+
assertNull(categoryProperty.getOneOf(), "The oneOf should have been removed.");
1302+
assertEquals(categoryProperty.get$ref(), "#/components/schemas/Category", "The $ref should be preserved.");
1303+
1304+
// Also check that metadata was preserved
1305+
assertEquals(categoryProperty.getDescription(), "This should become nullable'");
1306+
}
1307+
12541308
public static class RemoveRequiredNormalizer extends OpenAPINormalizer {
12551309

12561310
public RemoveRequiredNormalizer(OpenAPI openAPI, Map<String, String> inputRules) {

0 commit comments

Comments
 (0)