From 1d7d399ec290c41072760a8964745772d1db84a2 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Fri, 3 Oct 2025 11:06:39 +0800 Subject: [PATCH] fix additional properties handling in 3.1 spec (#22056) --- .../codegen/OpenAPINormalizer.java | 12 +- .../codegen/OpenAPINormalizerTest.java | 13 +++ .../codegen/utils/ModelUtilsTest.java | 41 +++++++ .../test/resources/3_1/null_schema_test.yaml | 107 ++++++++++++++++++ .../3_1/simplifyOneOfAnyOf_test.yaml | 12 ++ 5 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_1/null_schema_test.yaml 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 964749ab077..a53d9655a74 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 @@ -724,15 +724,17 @@ public class OpenAPINormalizer { if (skipNormalization(schema, visitedSchemas)) { return schema; } + + if (ModelUtils.isNullTypeSchema(openAPI, schema)) { + return schema; + } + markSchemaAsVisited(schema, visitedSchemas); if (ModelUtils.isArraySchema(schema)) { // array Schema result = normalizeArraySchema(schema); normalizeSchema(result.getItems(), visitedSchemas); return result; - } else if (schema.getAdditionalProperties() instanceof Schema) { // map - normalizeMapSchema(schema); - normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); } else if (ModelUtils.isOneOf(schema)) { // oneOf return normalizeOneOf(schema, visitedSchemas); } else if (ModelUtils.isAnyOf(schema)) { // anyOf @@ -769,6 +771,9 @@ public class OpenAPINormalizer { return schema; } else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { normalizeProperties(schema.getProperties(), visitedSchemas); + } else if (schema.getAdditionalProperties() instanceof Schema) { // map + normalizeMapSchema(schema); + normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); } else if (schema instanceof BooleanSchema) { normalizeBooleanSchema(schema, visitedSchemas); } else if (schema instanceof IntegerSchema) { @@ -1012,6 +1017,7 @@ public class OpenAPINormalizer { if (schema.getAnyOf() == null) { return schema; } + for (int i = 0; i < schema.getAnyOf().size(); i++) { // normalize anyOf sub schemas one by one Object item = schema.getAnyOf().get(i); 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 f401296ef7a..5b5dc4f5877 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 @@ -971,6 +971,11 @@ public class OpenAPINormalizerTest { Schema schema21 = openAPI.getComponents().getSchemas().get("SingleAnyOfTest"); assertEquals(schema21.getAnyOf().size(), 1); + Schema schema23 = openAPI.getComponents().getSchemas().get("PropertiesWithAnyOf"); + assertEquals(((Schema) schema23.getProperties().get("anyof_nullable_string")).getAnyOf().size(), 2); + assertEquals(((Schema) schema23.getProperties().get("anyof_nullable_number")).getAnyOf().size(), 2); + + // start the normalization Map options = new HashMap<>(); options.put("SIMPLIFY_ONEOF_ANYOF", "true"); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); @@ -1025,6 +1030,14 @@ public class OpenAPINormalizerTest { assertEquals(schema22.getAnyOf(), null); assertEquals(schema22.getTypes(), Set.of("string")); assertEquals(schema22.getEnum().size(), 2); + + Schema schema24 = openAPI.getComponents().getSchemas().get("PropertiesWithAnyOf"); + assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_string")).getAnyOf(), null); + assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_string")).getNullable(), true); + assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_string")).getTypes().size(), 1); + assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_number")).getAnyOf(), null); + assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_number")).getNullable(), true); + assertEquals(((Schema) schema24.getProperties().get("anyof_nullable_number")).getTypes().size(), 1); } @Test 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 fdefc16c5b4..48f1340cf13 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 @@ -623,6 +623,47 @@ public class ModelUtilsTest { assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); } + @Test + public void isNullTypeSchemaTestWith31Spec() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/null_schema_test.yaml"); + Map options = new HashMap<>(); + Schema schema = openAPI.getComponents().getSchemas().get("AnyOfStringArrayOfString"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + + schema = openAPI.getComponents().getSchemas().get("IntegerRef"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + + schema = openAPI.getComponents().getSchemas().get("OneOfAnyType"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + + schema = openAPI.getComponents().getSchemas().get("AnyOfAnyType"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + + schema = openAPI.getComponents().getSchemas().get("Parent"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + // the dummy property is a ref to integer + assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getProperties().get("dummy"))); + + schema = openAPI.getComponents().getSchemas().get("AnyOfTest"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + // first element (getAnyOf().get(0)) is a string. no need to test + assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(1))); + assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(2))); + assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(3))); + + schema = openAPI.getComponents().getSchemas().get("OneOfRef"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getOneOf().get(0))); + + schema = openAPI.getComponents().getSchemas().get("OneOfMultiRef"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getOneOf().get(0))); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getOneOf().get(1))); + + schema = openAPI.getComponents().getSchemas().get("JustDescription"); + assertFalse(ModelUtils.isNullTypeSchema(openAPI, schema)); + } + @Test public void isUnsupportedSchemaTest() { OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/unsupported_schema_test.yaml"); diff --git a/modules/openapi-generator/src/test/resources/3_1/null_schema_test.yaml b/modules/openapi-generator/src/test/resources/3_1/null_schema_test.yaml new file mode 100644 index 00000000000..1a3de1a1072 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/null_schema_test.yaml @@ -0,0 +1,107 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: Example + license: + name: MIT +servers: + - url: http://api.example.xyz/v1 +paths: + /person/display/{personId}: + get: + parameters: + - name: personId + in: path + required: true + description: The id of the person to retrieve + schema: + type: string + operationId: list + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/AnyOfTest" +components: + schemas: + AnyOfTest: + description: to test anyOf + anyOf: + - type: string + - type: 'null' + - type: null + - $ref: null + OneOfTest: + description: to test oneOf + oneOf: + - type: integer + - type: 'null' + - type: null + - $ref: null + OneOfTest2: + description: to test oneOf + oneOf: + - type: string + - type: 'null' + OneOfNullableTest: + description: to test oneOf nullable + oneOf: + - type: integer + - type: string + - $ref: null + Parent: + type: object + properties: + dummy: + $ref: '#/components/schemas/IntegerRef' + string_ref: + anyOf: + - $ref: '#/components/schemas/StringRef' + AnyOfStringArrayOfString: + anyOf: + - type: string + - type: array + items: + type: string + AnyOfAnyType: + anyOf: + - type: boolean + - type: array + items: {} + - type: object + - type: string + - type: number + - type: integer + AnyOfAnyTypeWithRef: + anyOf: + - type: boolean + - type: array + items: { } + - type: object + - type: string + - type: number + - $ref: '#/components/schemas/IntegerRef' + IntegerRef: + type: integer + StringRef: + type: string + OneOfAnyType: + oneOf: + - type: object + - type: boolean + - type: number + - type: string + - type: integer + - type: array + items: {} + OneOfRef: + oneOf: + - $ref: '#/components/schemas/IntegerRef' + OneOfMultiRef: + oneOf: + - $ref: '#/components/schemas/IntegerRef' + - $ref: '#/components/schemas/StringRef' + JustDescription: + description: A schema with just description 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 588abf3e84c..d093a7efc4d 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 @@ -77,6 +77,18 @@ components: oneOf: - $ref: '#/components/schemas/Number' - $ref: '#/components/schemas/Number2' + PropertiesWithAnyOf: + additionalProperties: false + type: object + properties: + anyof_nullable_string: + anyOf: + - type: string + - type: null + anyof_nullable_number: + anyOf: + - type: number + - type: null Number: enum: - one