From e36172090e4ed7c0658ec181a8217f392068c909 Mon Sep 17 00:00:00 2001 From: Phellippe Lima <120402907+phpinhei-te@users.noreply.github.com> Date: Tue, 30 Apr 2024 07:36:49 +0100 Subject: [PATCH] fix: ExampleGenerator for composed child schemas and array schemas (#18479) * fix: ExampleGenerator for composed child schemas and array schemas correctly * fix: refactor to remove code duplication * fix: fixes test assertion encoding * fix: adds doc to new method --- .../codegen/examples/ExampleGenerator.java | 38 ++++++++---- .../codegen/ExampleGeneratorTest.java | 58 +++++++++++++++++++ .../resources/3_0/example_generator_test.yaml | 33 +++++++++++ .../net8/FormModels/api/openapi.yaml | 6 ++ .../net8/NullReferenceTypes/api/openapi.yaml | 6 ++ .../net8/Petstore/api/openapi.yaml | 6 ++ .../net8/SourceGeneration/api/openapi.yaml | 6 ++ .../standard2.0/Petstore/api/openapi.yaml | 6 ++ .../standard2.0/Petstore/api/openapi.yaml | 6 ++ .../net4.7/Petstore/api/openapi.yaml | 6 ++ .../net4.8/Petstore/api/openapi.yaml | 6 ++ .../net7/EnumMappings/api/openapi.yaml | 6 ++ .../restsharp/net7/Petstore/api/openapi.yaml | 6 ++ .../ConditionalSerialization/api/openapi.yaml | 6 ++ .../standard2.0/Petstore/api/openapi.yaml | 6 ++ .../standard2.0/Petstore/api/openapi.yaml | 6 ++ 16 files changed, 195 insertions(+), 12 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/examples/ExampleGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/examples/ExampleGenerator.java index 21560e2b69fb..e5a041c2010a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/examples/ExampleGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/examples/ExampleGenerator.java @@ -19,7 +19,6 @@ package org.openapitools.codegen.examples; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.utils.ModelUtils; @@ -364,17 +363,7 @@ public class ExampleGenerator { return schema.getExample(); } else if (ModelUtils.isAllOf(schema) || ModelUtils.isAllOfWithProperties(schema)) { LOGGER.debug("Resolving allOf model '{}' to example", name); - List interfaces = schema.getAllOf(); - for (Schema composed : interfaces) { - traverseSchemaProperties(mediaType, composed, processedModels, values); - if (composed.get$ref() != null) { - String ref = ModelUtils.getSimpleRef(composed.get$ref()); - Schema resolved = ModelUtils.getSchema(openAPI, ref); - if (resolved != null) { - traverseSchemaProperties(mediaType, resolved, processedModels, values); - } - } - } + resolveAllOfSchemaProperties(mediaType, schema, processedModels, values); schema.setExample(values); return schema.getExample(); } else if (ModelUtils.isAnyOf(schema) || ModelUtils.isOneOf(schema)) { @@ -388,6 +377,8 @@ public class ExampleGenerator { return null; } return resolvePropertyToExample(name, mediaType, found.get(), processedModels); + } else if (ModelUtils.isArraySchema(schema)) { + return resolvePropertyToExample(schema.getName(), mediaType, schema, processedModels); } else { // TODO log an error message as the model does not have any properties return null; @@ -400,6 +391,29 @@ public class ExampleGenerator { Schema property = (Schema) schema.getProperties().get(propertyName.toString()); values.put(propertyName.toString(), resolvePropertyToExample(propertyName.toString(), mediaType, property, processedModels)); } + } else if (ModelUtils.isAllOf(schema) || ModelUtils.isAllOfWithProperties(schema)) { + resolveAllOfSchemaProperties(mediaType, schema, processedModels, values); + } + } + + /** + * Transverse and resolves all property examples for `allOf` composed schemas into `values` map object + * @param mediaType MIME type + * @param schema OAS schema + * @param processedModels Set containing all processed models + * @param values Example value map + */ + private void resolveAllOfSchemaProperties(String mediaType, Schema schema, Set processedModels, Map values) { + List interfaces = schema.getAllOf(); + for (Schema composed : interfaces) { + traverseSchemaProperties(mediaType, composed, processedModels, values); + if (composed.get$ref() != null) { + String ref = ModelUtils.getSimpleRef(composed.get$ref()); + Schema resolved = ModelUtils.getSchema(openAPI, ref); + if (resolved != null) { + traverseSchemaProperties(mediaType, resolved, processedModels, values); + } + } } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ExampleGeneratorTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ExampleGeneratorTest.java index 844756e9a06d..13b401ce2d4e 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ExampleGeneratorTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ExampleGeneratorTest.java @@ -152,6 +152,35 @@ public class ExampleGeneratorTest { assertEquals("200", examples.get(0).get("statusCode")); } + @Test + public void generateFromResponseSchemaWithArraySchema() { + OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml"); + + new InlineModelResolver().flatten(openAPI); + + ExampleGenerator exampleGenerator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI); + Set mediaTypeKeys = new TreeSet<>(); + mediaTypeKeys.add("application/json"); + List> examples = exampleGenerator.generateFromResponseSchema( + "200", + openAPI + .getPaths() + .get("/generate_from_response_schema_array_reference") + .getGet() + .getResponses() + .get("200") + .getContent() + .get("application/json") + .getSchema(), + mediaTypeKeys + ); + + assertEquals(1, examples.size()); + assertEquals("application/json", examples.get(0).get("contentType")); + assertEquals(String.format(Locale.ROOT, "[ {%n \"example_schema_property\" : \"example schema property value\"%n}, {%n \"example_schema_property\" : \"example schema property value\"%n} ]"), examples.get(0).get("example")); + assertEquals("200", examples.get(0).get("statusCode")); + } + @Test public void generateFromResponseSchemaWithModel() { OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml"); @@ -210,6 +239,35 @@ public class ExampleGeneratorTest { assertEquals("200", examples.get(0).get("statusCode")); } + @Test + public void generateFromResponseSchemaWithAllOfChildComposedModel() { + OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml"); + + new InlineModelResolver().flatten(openAPI); + + ExampleGenerator exampleGenerator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI); + Set mediaTypeKeys = new TreeSet<>(); + mediaTypeKeys.add("application/json"); + List> examples = exampleGenerator.generateFromResponseSchema( + "200", + openAPI + .getPaths() + .get("/generate_from_response_schema_with_allOf_child_composed_model") + .getGet() + .getResponses() + .get("200") + .getContent() + .get("application/json") + .getSchema(), + mediaTypeKeys + ); + + assertEquals(1, examples.size()); + assertEquals("application/json", examples.get(0).get("contentType")); + assertEquals(String.format(Locale.ROOT, "{%n \"example_schema_property_composed\" : \"example schema property value composed\",%n \"example_schema_property_composed_parent\" : \"example schema property value composed parent\",%n \"example_schema_property\" : \"example schema property value\"%n}"), examples.get(0).get("example")); + assertEquals("200", examples.get(0).get("statusCode")); + } + @Test public void generateFromResponseSchemaWithOneOfComposedModel() { OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml"); diff --git a/modules/openapi-generator/src/test/resources/3_0/example_generator_test.yaml b/modules/openapi-generator/src/test/resources/3_0/example_generator_test.yaml index d101a638c3eb..020edb73b406 100644 --- a/modules/openapi-generator/src/test/resources/3_0/example_generator_test.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/example_generator_test.yaml @@ -61,6 +61,16 @@ paths: items: type: string example: primitive types example value + /generate_from_response_schema_array_reference: + get: + operationId: generateFromResponseSchemaArrayReference + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ExampleArraySchema' /generate_from_response_schema_with_model: get: operationId: generateFromResponseSchemaWithModel @@ -81,6 +91,16 @@ paths: application/json: schema: $ref: '#/components/schemas/ExampleAllOfSchema' + /generate_from_response_schema_with_allOf_child_composed_model: + get: + operationId: generateFromResponseSchemaWithAllOfModel + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ExampleAllOfParentSchema' /generate_from_response_schema_with_anyOf_composed_model: get: operationId: generateFromResponseSchemaWithAnyOfModel @@ -131,6 +151,15 @@ components: example_schema_property_composed: type: string example: example schema property value composed + ExampleAllOfParentSchema: + type: object + allOf: + - $ref: '#/components/schemas/ExampleAllOfSchema' + - type: object + properties: + example_schema_property_composed_parent: + type: string + example: example schema property value composed parent ExampleAnyOfSchema: type: object anyOf: @@ -149,3 +178,7 @@ components: example_schema_property_composed: type: string example: example schema property value composed + ExampleArraySchema: + type: array + items: + $ref: '#/components/schemas/ExampleSchema' diff --git a/samples/client/petstore/csharp/generichost/net8/FormModels/api/openapi.yaml b/samples/client/petstore/csharp/generichost/net8/FormModels/api/openapi.yaml index e0dd3b46457a..1d1ef5d670a6 100644 --- a/samples/client/petstore/csharp/generichost/net8/FormModels/api/openapi.yaml +++ b/samples/client/petstore/csharp/generichost/net8/FormModels/api/openapi.yaml @@ -1247,6 +1247,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2714,6 +2718,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/generichost/net8/NullReferenceTypes/api/openapi.yaml b/samples/client/petstore/csharp/generichost/net8/NullReferenceTypes/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/generichost/net8/NullReferenceTypes/api/openapi.yaml +++ b/samples/client/petstore/csharp/generichost/net8/NullReferenceTypes/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/generichost/net8/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/generichost/net8/Petstore/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/generichost/net8/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/generichost/net8/Petstore/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/generichost/net8/SourceGeneration/api/openapi.yaml b/samples/client/petstore/csharp/generichost/net8/SourceGeneration/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/generichost/net8/SourceGeneration/api/openapi.yaml +++ b/samples/client/petstore/csharp/generichost/net8/SourceGeneration/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/generichost/standard2.0/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/generichost/standard2.0/Petstore/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/generichost/standard2.0/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/generichost/standard2.0/Petstore/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/httpclient/standard2.0/Petstore/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/restsharp/net4.7/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/restsharp/net4.7/Petstore/api/openapi.yaml index 673de491c712..6cfc875b0745 100644 --- a/samples/client/petstore/csharp/restsharp/net4.7/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/restsharp/net4.7/Petstore/api/openapi.yaml @@ -1304,6 +1304,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2894,6 +2898,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/restsharp/net4.8/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/restsharp/net4.8/Petstore/api/openapi.yaml index 673de491c712..6cfc875b0745 100644 --- a/samples/client/petstore/csharp/restsharp/net4.8/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/restsharp/net4.8/Petstore/api/openapi.yaml @@ -1304,6 +1304,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2894,6 +2898,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/restsharp/net7/EnumMappings/api/openapi.yaml b/samples/client/petstore/csharp/restsharp/net7/EnumMappings/api/openapi.yaml index 673de491c712..6cfc875b0745 100644 --- a/samples/client/petstore/csharp/restsharp/net7/EnumMappings/api/openapi.yaml +++ b/samples/client/petstore/csharp/restsharp/net7/EnumMappings/api/openapi.yaml @@ -1304,6 +1304,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2894,6 +2898,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/restsharp/net7/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/restsharp/net7/Petstore/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/restsharp/net7/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/restsharp/net7/Petstore/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/api/openapi.yaml b/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/api/openapi.yaml +++ b/samples/client/petstore/csharp/restsharp/standard2.0/ConditionalSerialization/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/api/openapi.yaml index 673de491c712..6cfc875b0745 100644 --- a/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/restsharp/standard2.0/Petstore/api/openapi.yaml @@ -1304,6 +1304,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2894,6 +2898,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string diff --git a/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/api/openapi.yaml b/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/api/openapi.yaml index a7e5643fd01c..0003c1c75269 100644 --- a/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/api/openapi.yaml +++ b/samples/client/petstore/csharp/unityWebRequest/standard2.0/Petstore/api/openapi.yaml @@ -1278,6 +1278,10 @@ components: type: array RolesReportsHash: description: Role report Hash + example: + role: + name: name + role_uuid: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 properties: role_uuid: format: uuid @@ -2862,6 +2866,8 @@ components: - country type: object RolesReportsHash_role: + example: + name: name properties: name: type: string