From 565c78b32e51eed9566e92fe4b4703e6b9653f38 Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:28:12 +0200 Subject: [PATCH] fix(normalizer): restore behavior of clearing a oneOf if the schema content cannot currently be used by the generator --- .../codegen/OpenAPINormalizer.java | 21 +++++--- .../codegen/utils/ModelUtils.java | 13 +++++ .../codegen/OpenAPINormalizerTest.java | 50 ++++++++++++++++--- .../3_1/simplifyOneOfAnyOf_test.yaml | 11 +++- .../latest/AnnotatedEnum/api/openapi.yaml | 2 + 5 files changed, 82 insertions(+), 15 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index 461311648c0c..c8c3efad5ea9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -43,6 +43,7 @@ import static org.openapitools.codegen.CodegenConstants.*; import static org.openapitools.codegen.utils.EnumUtils.ANY_OF; import static org.openapitools.codegen.utils.EnumUtils.ONE_OF; +import static org.openapitools.codegen.utils.ModelUtils.isOneOfOfConsts; import static org.openapitools.codegen.utils.ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema; import static org.openapitools.codegen.utils.StringUtils.getUniqueString; @@ -1705,18 +1706,24 @@ protected Schema processSimplifyOneOf(Schema schema) { } schema = simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, schema, oneOfSchemas); - if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema)) { - if (schema.getSpecVersion().equals(SpecVersion.V30)) { - schema.setOneOf(null); - } //else { - // TODO convert oneOf const/deprecated to enum - // } - } + clearOneOf(schema); } return schema; } + /** + * Removes the {@code oneOf} from the schema if it is considered to not contain information that the generator can + * currently act upon. The schema is left untouched if all the {@code oneOf} branches contain an OAS 3.1 {@code const}. + * This since that structure can potentially be used for enum interpretation. + */ + private void clearOneOf(Schema schema) { + if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema)) { + if (!isOneOfOfConsts(schema)) { + schema.setOneOf(null); + } + } + } /** * Ensure inheritance is correctly defined for OneOf and Discriminators. diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java index 5135dbad91cc..6b7fa7f469d5 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java @@ -2802,6 +2802,19 @@ public static boolean containsEnums(OpenAPI openAPI) { return schemaMap.values().stream().anyMatch(ModelUtils::isEnumSchema); } + /** + * Whether all branches in the oneOf contains a {@code const}. Returns false for OAS 3.0 since that does not support + * {@code const}. + * @param schema The Schema + * @return true if all {@code oneOf} branches contains a {@code const}. + */ + public static boolean isOneOfOfConsts(Schema schema) { + if (hasOneOf(schema) && !schema.getSpecVersion().equals(SpecVersion.V30)) { + return schema.getOneOf().stream().allMatch(oneOf -> oneOf.getConst() != null); + } + return false; + } + @FunctionalInterface private interface OpenAPISchemaVisitor { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java index e9e0ad0d9563..f62bbff8f9e1 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java @@ -34,6 +34,8 @@ public class OpenAPINormalizerTest { + private static final String SIMPLIFY_ONE_OF_ANY_OF = "SIMPLIFY_ONEOF_ANYOF"; + private static final String SIMPLIFY_ONEOF_ANYOF_ENUM = "SIMPLIFY_ONEOF_ANYOF_ENUM"; private static final String REF_AS_PARENT_IN_ALLOF = "REF_AS_PARENT_IN_ALLOF"; private static final String X_PARENT = "x-parent"; private static final String X_INTERNAL = "x-internal"; @@ -201,7 +203,7 @@ public void testSimplifyOneOfAnyOfEnum() throws Exception { // Test with rule enabled (default) Map options = new HashMap<>(); - options.put("SIMPLIFY_ONEOF_ANYOF_ENUM", "true"); + options.put(SIMPLIFY_ONEOF_ANYOF_ENUM, "true"); OpenAPINormalizer normalizer = new OpenAPINormalizer(openAPI, options); normalizer.normalize(); @@ -239,7 +241,7 @@ public void testSimplifyOneOfAnyOfEnum() throws Exception { // Test with rule disabled OpenAPI openAPI2 = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfWithEnums_test.yaml"); Map options2 = new HashMap<>(); - options2.put("SIMPLIFY_ONEOF_ANYOF_ENUM", "false"); + options2.put(SIMPLIFY_ONEOF_ANYOF_ENUM, "false"); OpenAPINormalizer normalizer2 = new OpenAPINormalizer(openAPI2, options2); normalizer2.normalize(); @@ -303,7 +305,7 @@ public void testOpenAPINormalizerSimplifyOneOfAnyOf() { assertEquals(schema19.getAnyOf().size(), 1); Map options = new HashMap<>(); - options.put("SIMPLIFY_ONEOF_ANYOF", "true"); + options.put(SIMPLIFY_ONE_OF_ANY_OF, "true"); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); openAPINormalizer.normalize(); @@ -361,7 +363,7 @@ public void testOpenAPINormalizerSimplifyOneOfWithSingleRef() { assertEquals(((Schema) oneOfWithSingleRef.getProperties().get("number")).getOneOf().size(), 1); Map options = new HashMap<>(); - options.put("SIMPLIFY_ONEOF_ANYOF", "true"); + options.put(SIMPLIFY_ONE_OF_ANY_OF, "true"); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); openAPINormalizer.normalize(); @@ -502,7 +504,7 @@ public void testOpenAPINormalizerConvertEnumNullToNullable() { assertNull(schema.getNullable()); Map options = new HashMap<>(); - options.put("SIMPLIFY_ONEOF_ANYOF", "true"); + options.put(SIMPLIFY_ONE_OF_ANY_OF, "true"); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); openAPINormalizer.normalize(); @@ -1530,7 +1532,7 @@ public void testOpenAPINormalizerSimplifyOneOfAnyOf31Spec() { // start the normalization Map options = new HashMap<>(); - options.put("SIMPLIFY_ONEOF_ANYOF", "true"); + options.put(SIMPLIFY_ONE_OF_ANY_OF, "true"); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); openAPINormalizer.normalize(); @@ -1604,6 +1606,40 @@ public void testOpenAPINormalizerSimplifyOneOfAnyOf31Spec() { assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_number")).getTypes().size(), 1); } + @Test + public void testOneOfWithStringsWithDifferentPatternsAreCollapsedWithSimplifyOneOfAnyOf() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml"); + + Schema stringPatternsWithOneOf = openAPI.getComponents().getSchemas().get("StringPatternsWithOneOf"); + assertEquals(stringPatternsWithOneOf.getOneOf().size(), 2); + + // start the normalization + Map options = new HashMap<>(); + options.put(SIMPLIFY_ONE_OF_ANY_OF, "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema normalizedStringPatternsWithOneOf = openAPI.getComponents().getSchemas().get("StringPatternsWithOneOf"); + assertNull(normalizedStringPatternsWithOneOf.getOneOf()); + } + + @Test + public void testOneOfWithConstsIsUntouchedBySimplifyOneOfAnyOf() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml"); + + Schema integerWithOneOfConsts = openAPI.getComponents().getSchemas().get("TypeIntegerWithOneOf"); + assertEquals(integerWithOneOfConsts.getOneOf().size(), 3); + + // start the normalization + Map options = new HashMap<>(); + options.put(SIMPLIFY_ONEOF_ANYOF_ENUM, "false"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema normalizedIntegerWithOneOfConsts = openAPI.getComponents().getSchemas().get("TypeIntegerWithOneOf"); + assertEquals(normalizedIntegerWithOneOfConsts.getOneOf().size(), 3); + } + @Test public void testOpenAPINormalizerSimplifyOneOfWithSingleRef31Spec() { OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml"); @@ -1612,7 +1648,7 @@ public void testOpenAPINormalizerSimplifyOneOfWithSingleRef31Spec() { assertEquals(((Schema) oneOfWithSingleRef.getProperties().get("number")).getOneOf().size(), 1); Map options = new HashMap<>(); - options.put("SIMPLIFY_ONEOF_ANYOF", "true"); + options.put(SIMPLIFY_ONE_OF_ANY_OF, "true"); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); openAPINormalizer.normalize(); diff --git a/modules/openapi-generator/src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml b/modules/openapi-generator/src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml index bf8dc407b9ef..6e6c028f8d91 100644 --- a/modules/openapi-generator/src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml +++ b/modules/openapi-generator/src/test/resources/3_1/simplifyOneOfAnyOf_test.yaml @@ -150,4 +150,13 @@ components: OneOfNullAndRef3: oneOf: - $ref: '#/components/schemas/Parent' - - type: "null" \ No newline at end of file + - type: "null" + StringPatternsWithOneOf: + type: string + oneOf: + - type: string + description: Numeric identifier + pattern: '^\d{1,35}$' + - type: string + description: UUID identifier + pattern: '^[0-9a-f-]{36}$' \ No newline at end of file diff --git a/samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/api/openapi.yaml b/samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/api/openapi.yaml index da6dae64572d..b657d7bc953c 100644 --- a/samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/api/openapi.yaml +++ b/samples/client/petstore/csharp/generichost/latest/AnnotatedEnum/api/openapi.yaml @@ -131,6 +131,8 @@ components: allOf: - $ref: "#/components/schemas/Parent" nullable: true + StringPatternsWithOneOf: + type: string ParentWithPluralOneOfProperty_number: oneOf: - $ref: "#/components/schemas/Number"