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 8b39c618816..05077a68cf0 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 @@ -665,7 +665,41 @@ public class OpenAPINormalizer { } } + /* + * Remove unsupported schemas (e.g. if, then) from allOf. + * + * @param schema Schema + */ + private void removeUnsupportedSchemasFromAllOf(Schema schema) { + if (schema.getAllOf() == null) { + return; + } + + Iterator iterator = schema.getAllOf().iterator(); + while (iterator.hasNext()) { + Schema item = iterator.next(); + + // remove unsupported schemas (e.g. if, then) + if (ModelUtils.isUnsupportedSchema(openAPI, item)) { + LOGGER.debug("Removed allOf sub-schema that's not yet supported: {}", item); + iterator.remove(); + } + } + + if (schema.getAllOf().size() == 0) { + // no more schema in allOf so reset to null instead + LOGGER.info("Unset/Removed allOf after cleaning up allOf sub-schemas that are not yet supported."); + schema.setAllOf(null); + } + } + private Schema normalizeAllOf(Schema schema, Set visitedSchemas) { + removeUnsupportedSchemasFromAllOf(schema); + + if (schema.getAllOf() == null) { + return schema; + } + for (Object item : schema.getAllOf()) { if (!(item instanceof Schema)) { throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); @@ -673,6 +707,7 @@ public class OpenAPINormalizer { // normalize allOf sub schemas one by one normalizeSchema((Schema) item, visitedSchemas); } + // process rules here processUseAllOfRefAsParent(schema); @@ -680,6 +715,12 @@ public class OpenAPINormalizer { } private Schema normalizeAllOfWithProperties(Schema schema, Set visitedSchemas) { + removeUnsupportedSchemasFromAllOf(schema); + + if (schema.getAllOf() == null) { + return schema; + } + for (Object item : schema.getAllOf()) { if (!(item instanceof Schema)) { throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); 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 a4ea495f755..c46de774632 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 @@ -616,6 +616,16 @@ public class ModelUtils { return ModelUtils.isArraySchema(schema) && Boolean.TRUE.equals(schema.getUniqueItems()); } + /** + * Return true if the schema is a string/integer/number/boolean type in OpenAPI. + * + * @param schema the OAS schema + * @return true if the schema is a string/integer/number/boolean type in OpenAPI. + */ + public static boolean isPrimitiveType(Schema schema) { + return (isStringSchema(schema) || isIntegerSchema(schema) || isNumberSchema(schema) || isBooleanSchema(schema)); + } + public static boolean isStringSchema(Schema schema) { return schema instanceof StringSchema || SchemaTypeUtil.STRING_TYPE.equals(getType(schema)); } @@ -2262,6 +2272,35 @@ public class ModelUtils { return false; } + /** + * Check if the schema is supported by OpenAPI Generator. + *

+ * Return true if the schema can be handled by OpenAPI Generator + * + * @param schema Schema + * @param openAPI OpenAPIs + * + * @return true if schema is null type + */ + public static boolean isUnsupportedSchema(OpenAPI openAPI, Schema schema) { + if (schema == null) { + return true; + } + + // dereference the schema + schema = ModelUtils.getReferencedSchema(openAPI, schema); + + if (schema.getTypes() == null && hasValidation(schema)) { + // just validation without type + return true; + } else if (schema.getIf() != null && schema.getThen() != null) { + // if, then in 3.1 spec + return true; + } + + 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 442841c5dd6..e7eb0b02f35 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 @@ -774,4 +774,24 @@ public class OpenAPINormalizerTest { assertEquals(normalizedTypeSchema.getEnum().size(), 1); assertEquals(Arrays.asList(originalConst), normalizedTypeSchema.getEnum()); } + + @Test + public void testOpenAPINormalizerProcessingAllOfSchema31Spec() { + // to test array schema processing in 3.1 spec + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/unsupported_schema_test.yaml"); + + Schema schema = openAPI.getComponents().getSchemas().get("Dummy"); + assertEquals(((Schema) schema.getProperties().get("property1")).getAllOf().size(), 2); + assertNotEquals(((Schema) ((Schema) schema.getProperties().get("property2")).getAllOf().get(0)).getIf(), null); // if is set before normalization + assertNotEquals(((Schema) ((Schema) schema.getProperties().get("property2")).getAllOf().get(1)).getThen(), null); // then is set before normalization + + Map inputRules = Map.of("NORMALIZE_31SPEC", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, inputRules); + openAPINormalizer.normalize(); + + Schema schema2 = openAPI.getComponents().getSchemas().get("Dummy"); + assertEquals(((Schema) schema2.getProperties().get("property1")).getAllOf(), null); + assertEquals(((Schema) schema2.getProperties().get("property2")).getAllOf(), null); + assertEquals(((Schema) schema2.getProperties().get("property2")).getAllOf(), null); + } } 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 8786b9b3f10..05a3cb48d54 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 @@ -501,4 +501,26 @@ public class ModelUtilsTest { assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(2))); assertTrue(ModelUtils.isNullTypeSchema(openAPI, (Schema) schema.getAnyOf().get(3))); } + + @Test + public void isUnsupportedSchemaTest() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/unsupported_schema_test.yaml"); + Map options = new HashMap<>(); + Schema schema = openAPI.getComponents().getSchemas().get("Dummy"); + Schema property1 = (Schema) schema.getProperties().get("property1"); + Schema property2 = (Schema) schema.getProperties().get("property2"); + + // a test of string type with allOf (2 patterns) + assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property1.getAllOf().get(0))); + assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property1.getAllOf().get(1))); + + // if, then test + assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getAllOf().get(0))); + assertTrue(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getAllOf().get(1))); + + // typical schemas, e.g. boolean, string, array of string (enum) + assertFalse(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getProperties().get("aBooleanCheck"))); + assertFalse(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getProperties().get("condition"))); + assertFalse(ModelUtils.isUnsupportedSchema(openAPI, (Schema) property2.getProperties().get("purpose"))); + } } diff --git a/modules/openapi-generator/src/test/resources/3_1/unsupported_schema_test.yaml b/modules/openapi-generator/src/test/resources/3_1/unsupported_schema_test.yaml new file mode 100644 index 00000000000..997f8f6a139 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_1/unsupported_schema_test.yaml @@ -0,0 +1,64 @@ +openapi: 3.1.0 +info: + version: 1.0.0 + title: Example +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/Dummy" +components: + schemas: + Dummy: + type: object + properties: + property1: + type: string + allOf: + - pattern: "[abc]" + - pattern: "[a-z]" + property2: + type: object + allOf: + - if: + properties: + aBooleanCheck: + const: false + then: + required: + - condition + - if: + properties: + aBooleanCheck: + const: true + then: + required: + - purpose + properties: + aBooleanCheck: + type: boolean + condition: + type: string + purpose: + type: array + items: + type: string + enum: + - FIRST + - SECOND + - THIRD \ No newline at end of file