diff --git a/docs/customization.md b/docs/customization.md index c63f71472ce..1e21e9494f2 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -484,3 +484,10 @@ Example: ``` java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/simplifyBooleanEnum_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer SIMPLIFY_BOOLEAN_ENUM=true ``` + +- `SIMPLIFY_ONEOF_ANYOF`: when set to `true`, simplify oneOf/anyOf by 1) removing null (sub-schema) and setting nullable to true instead, and 2) simplifying oneOf/anyOf with a single sub-schema to just the sub-schema itself. + +Example: +``` +java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer SIMPLIFY_ONEOF_ANYOF=true +``` 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 639e1bdac2d..9a2dbd17bb5 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 @@ -63,6 +63,11 @@ public class OpenAPINormalizer { final String SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING = "SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING"; boolean simplifyAnyOfStringAndEnumString; + // when set to true, oneOf/anyOf schema with only one sub-schema is simplified to just the sub-schema + // and if sub-schema contains "null", remove it and set nullable to true instead + final String SIMPLIFY_ONEOF_ANYOF = "SIMPLIFY_ONEOF_ANYOF"; + boolean simplifyOneOfAnyOf; + // when set to true, boolean enum will be converted to just boolean final String SIMPLIFY_BOOLEAN_ENUM = "SIMPLIFY_BOOLEAN_ENUM"; boolean simplifyBooleanEnum; @@ -111,6 +116,10 @@ public class OpenAPINormalizer { simplifyAnyOfStringAndEnumString = true; } + if (enableAll || "true".equalsIgnoreCase(rules.get(SIMPLIFY_ONEOF_ANYOF))) { + simplifyOneOfAnyOf = true; + } + if (enableAll || "true".equalsIgnoreCase(rules.get(SIMPLIFY_BOOLEAN_ENUM))) { simplifyBooleanEnum = true; } @@ -390,27 +399,36 @@ public class OpenAPINormalizer { private Schema normalizeOneOf(Schema schema, Set visitedSchemas) { for (Object item : schema.getOneOf()) { + if (item == null) { + continue; + } if (!(item instanceof Schema)) { - throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); + throw new RuntimeException("Error! oneOf schema is not of the type Schema: " + item); } // normalize oenOf sub schemas one by one normalizeSchema((Schema) item, visitedSchemas); } - // process rules here + schema = processSimplifyOneOf(schema); + return schema; } private Schema normalizeAnyOf(Schema schema, Set visitedSchemas) { for (Object item : schema.getAnyOf()) { + if (item == null) { + continue; + } + if (!(item instanceof Schema)) { - throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); + throw new RuntimeException("Error! anyOf schema is not of the type Schema: " + item); } // normalize anyOf sub schemas one by one normalizeSchema((Schema) item, visitedSchemas); } // process rules here + schema = processSimplifyAnyOf(schema); // last rule to process as the schema may become String schema (not "anyOf") after the completion return processSimplifyAnyOfStringAndEnumString(schema); @@ -506,7 +524,7 @@ public class OpenAPINormalizer { /** * If the schema is anyOf and the sub-schemas are either string or enum of string, - * then simply it to just string as many generators do not yet support anyOf. + * then simplify it to just string as many generators do not yet support anyOf. * * @param schema Schema * @return Schema @@ -541,6 +559,72 @@ public class OpenAPINormalizer { } } + /** + * If the schema is oneOf and the sub-schemas is null, set `nullable: true` instead. + * If there's only one sub-schema, simply return the sub-schema directly. + * + * @param schema Schema + * @return Schema + */ + private Schema processSimplifyOneOf(Schema schema) { + if (!simplifyOneOfAnyOf && !enableAll) { + return schema; + } + + if (schema.getOneOf() != null && !schema.getOneOf().isEmpty()) { + // convert null sub-schema to `nullable: true` + for (int i = 0; i < schema.getOneOf().size(); i++) { + if (schema.getOneOf().get(i) == null || ((Schema) schema.getOneOf().get(i)).getType() == null) { + schema.getOneOf().remove(i); + schema.setNullable(true); + } + } + + // if only one element left, simplify to just the element (schema) + if (schema.getOneOf().size() == 1) { + if (schema.getNullable()) { // retain nullable setting + ((Schema) schema.getOneOf().get(0)).setNullable(true); + } + return (Schema) schema.getOneOf().get(0); + } + } + + return schema; + } + + /** + * If the schema is anyOf and the sub-schemas is null, set `nullable: true` instead. + * If there's only one sub-schema, simply return the sub-schema directly. + * + * @param schema Schema + * @return Schema + */ + private Schema processSimplifyAnyOf(Schema schema) { + if (!simplifyOneOfAnyOf && !enableAll) { + return schema; + } + + if (schema.getAnyOf() != null && !schema.getAnyOf().isEmpty()) { + // convert null sub-schema to `nullable: true` + for (int i = 0; i < schema.getAnyOf().size(); i++) { + if (schema.getAnyOf().get(i) == null || ((Schema) schema.getAnyOf().get(i)).getType() == null) { + schema.getAnyOf().remove(i); + schema.setNullable(true); + } + } + + // if only one element left, simplify to just the element (schema) + if (schema.getAnyOf().size() == 1) { + if (schema.getNullable()) { // retain nullable setting + ((Schema) schema.getAnyOf().get(0)).setNullable(true); + } + return (Schema) schema.getAnyOf().get(0); + } + } + + return schema; + } + /** * If the schema is boolean and its enum is defined, * then simply it to just boolean. diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java index 103c8a05cb7..b656e8c1c91 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java @@ -4395,6 +4395,42 @@ public class DefaultCodegenTest { assertTrue(schema3 instanceof StringSchema); } + @Test + public void testOpenAPINormalizerSimplifyOneOfAnyOf() { + // to test the rule SIMPLIFY_ONEOF_ANYOF + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml"); + + Schema schema = openAPI.getComponents().getSchemas().get("AnyOfTest"); + assertEquals(schema.getAnyOf().size(), 2); + assertNull(schema.getNullable()); + + Schema schema2 = openAPI.getComponents().getSchemas().get("OneOfTest"); + assertEquals(schema2.getOneOf().size(), 2); + assertNull(schema2.getNullable()); + + Schema schema5 = openAPI.getComponents().getSchemas().get("OneOfNullableTest"); + assertEquals(schema5.getOneOf().size(), 3); + assertNull(schema5.getNullable()); + + Map options = new HashMap<>(); + options.put("SIMPLIFY_ONEOF_ANYOF", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema schema3 = openAPI.getComponents().getSchemas().get("AnyOfTest"); + assertNull(schema3.getAnyOf()); + assertTrue(schema3 instanceof StringSchema); + assertTrue(schema3.getNullable()); + + Schema schema4 = openAPI.getComponents().getSchemas().get("OneOfTest"); + assertNull(schema4.getOneOf()); + assertTrue(schema4 instanceof IntegerSchema); + + Schema schema6 = openAPI.getComponents().getSchemas().get("OneOfNullableTest"); + assertEquals(schema6.getOneOf().size(), 2); + assertTrue(schema6.getNullable()); + } + @Test public void testOpenAPINormalizerSimplifyBooleanEnum() { // to test the rule SIMPLIFY_BOOLEAN_ENUM 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 new file mode 100644 index 00000000000..5f0cf2370de --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/simplifyOneOfAnyOf_test.yaml @@ -0,0 +1,45 @@ +openapi: 3.0.1 +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 + OneOfTest: + description: to test oneOf + oneOf: + - type: integer + - $ref: null + OneOfNullableTest: + description: to test oneOf nullable + oneOf: + - type: integer + - type: string + - $ref: null +