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 aa3874d7610..4afcc589d12 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 @@ -642,23 +642,30 @@ public class OpenAPINormalizer { } private Schema normalizeOneOf(Schema schema, Set visitedSchemas) { - for (int i = 0; i < schema.getOneOf().size(); i++) { - // normalize oneOf sub schemas one by one - Object item = schema.getOneOf().get(i); - - if (item == null) { - continue; - } - if (!(item instanceof Schema)) { - throw new RuntimeException("Error! oneOf schema is not of the type Schema: " + item); - } - - // update sub-schema with the updated schema - schema.getOneOf().set(i, normalizeSchema((Schema) item, visitedSchemas)); - } - // process rules here + // simplify first as the schema may no longer be a oneOf after processing the rule below schema = processSimplifyOneOf(schema); + // if it's still a oneOf, loop through the sub-schemas + if (schema.getOneOf() != null) { + for (int i = 0; i < schema.getOneOf().size(); i++) { + // normalize oneOf sub schemas one by one + Object item = schema.getOneOf().get(i); + + if (item == null) { + continue; + } + if (!(item instanceof Schema)) { + throw new RuntimeException("Error! oneOf schema is not of the type Schema: " + item); + } + + // update sub-schema with the updated schema + schema.getOneOf().set(i, normalizeSchema((Schema) item, visitedSchemas)); + } + } else { + // normalize it as it's no longer an oneOf + schema = normalizeSchema(schema, visitedSchemas); + } + return schema; } @@ -683,7 +690,7 @@ public class OpenAPINormalizer { schema = processSimplifyAnyOf(schema); // last rule to process as the schema may become String schema (not "anyOf") after the completion - return processSimplifyAnyOfStringAndEnumString(schema); + return normalizeSchema(processSimplifyAnyOfStringAndEnumString(schema), visitedSchemas); } private Schema normalizeComplexComposedSchema(Schema schema, Set visitedSchemas) { @@ -694,7 +701,7 @@ public class OpenAPINormalizer { processRemoveAnyOfOneOfAndKeepPropertiesOnly(schema); - return schema; + return normalizeSchema(schema, visitedSchemas); } // ===================== a list of rules ===================== @@ -893,21 +900,40 @@ public class OpenAPINormalizer { } /** - * Check if the schema is of type 'null' + * Check if the schema is of type 'null' or schema itself is pointing to null *

* Return true if the schema's type is 'null' or not specified * * @param schema Schema + * @param openAPI OpenAPI + * + * @return true if schema is null type */ - public boolean isNullTypeSchema(Schema schema) { + public boolean isNullTypeSchema(OpenAPI openAPI, Schema schema) { if (schema == null) { return true; } + // dereference the schema + schema = ModelUtils.getReferencedSchema(openAPI, schema); + + // allOf/anyOf/oneOf if (ModelUtils.hasAllOf(schema) || ModelUtils.hasOneOf(schema) || ModelUtils.hasAnyOf(schema)) { return false; } + // schema with properties + if (schema.getProperties() != null) { + return false; + } + + // convert referenced enum of null only to `nullable:true` + if (schema.getEnum() != null && schema.getEnum().size() == 1) { + if ("null".equals(String.valueOf(schema.getEnum().get(0)))) { + return true; + } + } + if (schema.getTypes() != null && !schema.getTypes().isEmpty()) { // 3.1 spec if (schema.getTypes().size() == 1) { // 1 type only @@ -933,14 +959,6 @@ public class OpenAPINormalizer { } } - // convert referenced enum of null only to `nullable:true` - Schema referencedSchema = ModelUtils.getReferencedSchema(openAPI, schema); - if (referencedSchema.getEnum() != null && referencedSchema.getEnum().size() == 1) { - if ("null".equals(String.valueOf(referencedSchema.getEnum().get(0)))) { - return true; - } - } - return false; } @@ -986,7 +1004,7 @@ public class OpenAPINormalizer { } } - if (oneOfSchemas.removeIf(oneOf -> isNullTypeSchema(oneOf))) { + if (oneOfSchemas.removeIf(oneOf -> isNullTypeSchema(openAPI, oneOf))) { schema.setNullable(true); // if only one element left, simplify to just the element (schema) @@ -997,6 +1015,11 @@ public class OpenAPINormalizer { return (Schema) oneOfSchemas.get(0); } } + + if (ModelUtils.isIntegerSchema(schema) || ModelUtils.isNumberSchema(schema) || ModelUtils.isStringSchema(schema)) { + // TODO convert oneOf const to enum + schema.setOneOf(null); + } } return schema; @@ -1117,7 +1140,7 @@ public class OpenAPINormalizer { } } - if (anyOfSchemas.removeIf(anyOf -> isNullTypeSchema(anyOf))) { + if (anyOfSchemas.removeIf(anyOf -> isNullTypeSchema(openAPI, anyOf))) { schema.setNullable(true); } 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 641e935f2b6..325784d8d7f 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 @@ -113,7 +113,7 @@ public class OpenAPINormalizerTest { Map options = new HashMap<>(); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); Schema schema = openAPI.getComponents().getSchemas().get("AnyOfStringArrayOfString"); - assertFalse(openAPINormalizer.isNullTypeSchema(schema)); + assertFalse(openAPINormalizer.isNullTypeSchema(openAPI, schema)); } @Test @@ -705,6 +705,12 @@ public class OpenAPINormalizerTest { Schema schema13 = openAPI.getComponents().getSchemas().get("OneOfAnyType"); assertEquals(schema13.getOneOf().size(), 6); + Schema schema15 = openAPI.getComponents().getSchemas().get("TypeIntegerWithOneOf"); + assertEquals(schema15.getOneOf().size(), 3); + + Schema schema17 = openAPI.getComponents().getSchemas().get("OneOfNullAndRef3"); + assertEquals(schema17.getOneOf().size(), 2); + Map options = new HashMap<>(); options.put("SIMPLIFY_ONEOF_ANYOF", "true"); OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); @@ -742,5 +748,14 @@ public class OpenAPINormalizerTest { Schema schema14 = openAPI.getComponents().getSchemas().get("OneOfAnyType"); assertEquals(schema14.getOneOf(), null); assertEquals(schema14.getType(), null); + + Schema schema16 = openAPI.getComponents().getSchemas().get("TypeIntegerWithOneOf"); + // oneOf should have been removed as the schema is essentially a primitive type + assertEquals(schema16.getOneOf(), null); + + Schema schema18 = openAPI.getComponents().getSchemas().get("OneOfNullAndRef3"); + // original oneOf removed and simplified to just $ref (oneOf sub-schema) instead + assertEquals(schema18.getOneOf(), null); + assertEquals(schema18.get$ref(), "#/components/schemas/Parent"); } } 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 a3463b4b12c..588abf3e84c 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 @@ -112,3 +112,29 @@ components: - type: integer - type: array items: {} + TypeIntegerWithOneOf: + type: integer + oneOf: + - title: ITEM A + description: This permission is for item A. + const: 1 + - title: ITEM B + description: This permission is for item B. + const: 2 + - title: ITEM C + description: This permission is for item C. + const: 4 + format: int32 + # need to repeat the issue when it only occurs with the 3rd, 4th, 5th, etc schemas with oneOf(type: null, $ref) + OneOfNullAndRef: + oneOf: + - $ref: '#/components/schemas/Parent' + - type: "null" + OneOfNullAndRef2: + oneOf: + - $ref: '#/components/schemas/Parent' + - type: "null" + OneOfNullAndRef3: + oneOf: + - $ref: '#/components/schemas/Parent' + - type: "null" \ No newline at end of file