From 490de02971fec3190ae456d78d896bb10706fcac Mon Sep 17 00:00:00 2001 From: Mattias Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Sun, 17 Aug 2025 18:36:14 +0200 Subject: [PATCH] Fix so that the oneOfAnyOf normalizer retains the read/write only attribute (#21737) --- .../codegen/OpenAPINormalizer.java | 7 +- .../codegen/utils/ModelUtils.java | 13 +++- .../codegen/utils/ModelUtilsTest.java | 68 +++++++++++++++++-- .../3_0/simplifyOneOfAnyOf_test.yaml | 28 +++++++- 4 files changed, 102 insertions(+), 14 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 b8a09f99a0b..7a95434248a 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 @@ -33,12 +33,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import static org.openapitools.codegen.utils.ModelUtils.simplyOneOfAnyOfWithOnlyOneNonNullSubSchema; +import static org.openapitools.codegen.utils.ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema; import static org.openapitools.codegen.utils.StringUtils.getUniqueString; public class OpenAPINormalizer { @@ -1318,7 +1317,7 @@ public class OpenAPINormalizer { } } - schema = simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, schema, oneOfSchemas); + schema = simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, schema, oneOfSchemas); if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema)) { // TODO convert oneOf const to enum @@ -1445,7 +1444,7 @@ public class OpenAPINormalizer { } } - schema = simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, schema, anyOfSchemas); + schema = simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, schema, anyOfSchemas); } return schema; 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 2c79c8a6ca8..75123e86a30 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 @@ -2227,17 +2227,24 @@ public class ModelUtils { * @param subSchemas The oneOf or AnyOf schemas * @return The simplified schema */ - public static Schema simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(OpenAPI openAPI, Schema schema, List subSchemas) { + public static Schema simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(OpenAPI openAPI, Schema schema, List subSchemas) { if (subSchemas.removeIf(subSchema -> isNullTypeSchema(openAPI, subSchema))) { schema.setNullable(true); } // if only one element left, simplify to just the element (schema) if (subSchemas.size() == 1) { + Schema subSchema = subSchemas.get(0); if (Boolean.TRUE.equals(schema.getNullable())) { // retain nullable setting - subSchemas.get(0).setNullable(true); + subSchema.setNullable(true); } - return subSchemas.get(0); + if (Boolean.TRUE.equals(schema.getReadOnly())) { + subSchema.setReadOnly(true); + } + if (Boolean.TRUE.equals(schema.getWriteOnly())) { + subSchema.setWriteOnly(true); + } + return subSchema; } return schema; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ModelUtilsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ModelUtilsTest.java index a0b17a44788..621e80de710 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ModelUtilsTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/ModelUtilsTest.java @@ -476,14 +476,14 @@ public class ModelUtilsTest { } @Test - public void simplyOneOfAnyOfWithOnlyOneNonNullSubSchema() { + public void simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema() { OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml"); Schema schema; List subSchemas; Schema anyOfWithSeveralSubSchemasButSingleNonNull = ModelUtils.getSchema(openAPI, "AnyOfTest"); subSchemas = anyOfWithSeveralSubSchemasButSingleNonNull.getAnyOf(); - schema = ModelUtils.simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSeveralSubSchemasButSingleNonNull, subSchemas); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSeveralSubSchemasButSingleNonNull, subSchemas); assertNull(schema.getOneOf()); assertNull(schema.getAnyOf()); assertTrue(schema.getNullable()); @@ -491,7 +491,7 @@ public class ModelUtilsTest { Schema anyOfWithSingleNonNullSubSchema = ModelUtils.getSchema(openAPI, "Parent"); subSchemas = ((Schema) anyOfWithSingleNonNullSubSchema.getProperties().get("number")).getAnyOf(); - schema = ModelUtils.simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSingleNonNullSubSchema, subSchemas); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, anyOfWithSingleNonNullSubSchema, subSchemas); assertNull(schema.getOneOf()); assertNull(schema.getAnyOf()); assertNull(schema.getNullable()); @@ -499,7 +499,7 @@ public class ModelUtilsTest { Schema oneOfWithSeveralSubSchemasButSingleNonNull = ModelUtils.getSchema(openAPI, "OneOfTest"); subSchemas = oneOfWithSeveralSubSchemasButSingleNonNull.getOneOf(); - schema = ModelUtils.simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemasButSingleNonNull, subSchemas); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemasButSingleNonNull, subSchemas); assertNull(schema.getOneOf()); assertNull(schema.getAnyOf()); assertTrue(schema.getNullable()); @@ -507,7 +507,7 @@ public class ModelUtilsTest { Schema oneOfWithSingleNonNullSubSchema = ModelUtils.getSchema(openAPI, "ParentWithOneOfProperty"); subSchemas = ((Schema) oneOfWithSingleNonNullSubSchema.getProperties().get("number")).getOneOf(); - schema = ModelUtils.simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSingleNonNullSubSchema, subSchemas); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSingleNonNullSubSchema, subSchemas); assertNull(schema.getOneOf()); assertNull(schema.getAnyOf()); assertNull(schema.getNullable()); @@ -515,7 +515,7 @@ public class ModelUtilsTest { Schema oneOfWithSeveralSubSchemas = ModelUtils.getSchema(openAPI, "ParentWithPluralOneOfProperty"); subSchemas = ((Schema) oneOfWithSeveralSubSchemas.getProperties().get("number")).getOneOf(); - schema = ModelUtils.simplyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemas, subSchemas); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, oneOfWithSeveralSubSchemas, subSchemas); assertNull(schema.getOneOf()); assertNotNull(oneOfWithSeveralSubSchemas.getProperties().get("number")); assertNull(schema.getAnyOf()); @@ -523,6 +523,62 @@ public class ModelUtilsTest { assertEquals(((Schema) oneOfWithSeveralSubSchemas.getProperties().get("number")).getOneOf().size(), 2); } + @Test + public void simplifyOneOfWithOnlyOneNonNullSubSchemaKeepsReadOnlyWriteOnlyAttribute() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml"); + Schema schema; + List subSchemas; + + Schema oneOfWithNullAndRefSubSchema = ModelUtils.getSchema(openAPI, "OneOfParentRefTest"); + Schema numberPropertySchema = ((Schema) oneOfWithNullAndRefSubSchema.getProperties().get("number")); + subSchemas = numberPropertySchema.getOneOf(); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, numberPropertySchema, subSchemas); + assertNull(schema.getOneOf()); + assertNull(schema.getAnyOf()); + assertTrue(schema.getNullable()); + assertNull(schema.getReadOnly()); + assertTrue(schema.getWriteOnly()); + assertEquals(schema.get$ref(), "#/components/schemas/IntegerRef"); + + Schema number2PropertySchema = ((Schema) oneOfWithNullAndRefSubSchema.getProperties().get("number2")); + subSchemas = number2PropertySchema.getOneOf(); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, number2PropertySchema, subSchemas); + assertNull(schema.getOneOf()); + assertNull(schema.getAnyOf()); + assertTrue(schema.getNullable()); + assertTrue(schema.getReadOnly()); + assertNull(schema.getWriteOnly()); + assertEquals(schema.get$ref(), "#/components/schemas/IntegerRef"); + } + + @Test + public void simplifyAnyOfWithOnlyOneNonNullSubSchemaKeepsReadOnlyWriteOnlyAttribute() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml"); + Schema schema; + List subSchemas; + + Schema anyOfWithNullAndRefSubSchema = ModelUtils.getSchema(openAPI, "AnyOfParentRefTest"); + Schema numberPropertySchema = ((Schema) anyOfWithNullAndRefSubSchema.getProperties().get("number")); + subSchemas = numberPropertySchema.getAnyOf(); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, numberPropertySchema, subSchemas); + assertNull(schema.getOneOf()); + assertNull(schema.getAnyOf()); + assertTrue(schema.getNullable()); + assertNull(schema.getReadOnly()); + assertTrue(schema.getWriteOnly()); + assertEquals(schema.get$ref(), "#/components/schemas/IntegerRef"); + + Schema number2PropertySchema = ((Schema) anyOfWithNullAndRefSubSchema.getProperties().get("number2")); + subSchemas = number2PropertySchema.getAnyOf(); + schema = ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema(openAPI, number2PropertySchema, subSchemas); + assertNull(schema.getOneOf()); + assertNull(schema.getAnyOf()); + assertTrue(schema.getNullable()); + assertTrue(schema.getReadOnly()); + assertNull(schema.getWriteOnly()); + assertEquals(schema.get$ref(), "#/components/schemas/IntegerRef"); + } + @Test public void isNullTypeSchemaTest() { OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/null_schema_test.yaml"); diff --git a/modules/openapi-generator/src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml b/modules/openapi-generator/src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml index f057ce6aea9..ee1c5e2ce44 100644 --- a/modules/openapi-generator/src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml @@ -122,4 +122,30 @@ components: - type: string - type: integer - type: array - items: {} \ No newline at end of file + items: {} + AnyOfParentRefTest: + type: object + properties: + number: + anyOf: + - type: null + - $ref: '#/components/schemas/IntegerRef' + writeOnly: true + number2: + anyOf: + - type: null + - $ref: '#/components/schemas/IntegerRef' + readOnly: true + OneOfParentRefTest: + type: object + properties: + number: + oneOf: + - type: null + - $ref: '#/components/schemas/IntegerRef' + writeOnly: true + number2: + oneOf: + - type: null + - $ref: '#/components/schemas/IntegerRef' + readOnly: true \ No newline at end of file