From 61c83926bdb3f1b072b89b379a9aa1596a173a77 Mon Sep 17 00:00:00 2001 From: Oleh Kurpiak Date: Fri, 15 Jul 2022 11:37:07 +0300 Subject: [PATCH] Split operation with multiple return types --- .../openapitools/codegen/cmd/Generate.java | 9 ++ .../gradle/plugin/OpenApiGeneratorPlugin.kt | 1 + .../OpenApiGeneratorGenerateExtension.kt | 6 + .../gradle/plugin/tasks/GenerateTask.kt | 11 ++ .../codegen/plugin/CodeGenMojo.java | 7 + .../codegen/CodegenConstants.java | 3 + .../openapitools/codegen/DefaultCodegen.java | 19 ++- .../codegen/DefaultGenerator.java | 139 ++++++++++++++---- .../java/spring/SpringCodegenTest.java | 109 ++++++++++++++ .../src/test/resources/bugs/issue_8700.yaml | 107 ++++++++++++++ 10 files changed, 375 insertions(+), 36 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/bugs/issue_8700.yaml diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java index 1a7495cf26fe..5a43f5c5cf4e 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java @@ -32,6 +32,7 @@ import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.*; import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.config.GlobalSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,6 +122,10 @@ public class Generate extends OpenApiGeneratorCommand { description = CodegenConstants.MODEL_NAME_SUFFIX_DESC) private String modelNameSuffix; + @Option(name = {"--split-response-types"}, title = "split response types", + description = CodegenConstants.SPLIT_RESPONSE_TYPES_DESC) + private Boolean splitResponseTypes; + @Option( name = {"--instantiation-types"}, title = "instantiation types", @@ -357,6 +362,10 @@ public void execute() { configurator.setModelNameSuffix(modelNameSuffix); } + if (splitResponseTypes != null) { + GlobalSettings.setProperty(CodegenConstants.SPLIT_RESPONSE_TYPES, splitResponseTypes.toString()); + } + if (isNotEmpty(invokerPackage)) { configurator.setInvokerPackage(invokerPackage); } diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt index 210fd17678c5..30839ddd4f6b 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt @@ -135,6 +135,7 @@ class OpenApiGeneratorPlugin : Plugin { generateModelDocumentation.set(generate.generateModelDocumentation) generateApiTests.set(generate.generateApiTests) generateApiDocumentation.set(generate.generateApiDocumentation) + splitOperation.set(generate.splitOperation) withXml.set(generate.withXml) configOptions.set(generate.configOptions) logToStderr.set(generate.logToStderr) diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt index 557f6386dc0b..5f15efba2676 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt @@ -286,6 +286,11 @@ open class OpenApiGeneratorGenerateExtension(project: Project) { */ val generateApiDocumentation = project.objects.property() + /** + * Split operations that have multiple response content-types into separate operations + */ + val splitOperation = project.objects.property() + /** * A special-case setting which configures some generators with XML support. In some cases, * this forces json OR xml, so the default here is false. @@ -341,6 +346,7 @@ open class OpenApiGeneratorGenerateExtension(project: Project) { generateModelDocumentation.set(true) generateApiTests.set(true) generateApiDocumentation.set(true) + splitOperation.set(false) withXml.set(false) configOptions.set(mapOf()) validateSpec.set(true) diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt index 69cb92e4bd3d..ebe32515efdc 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt @@ -410,6 +410,13 @@ open class GenerateTask : DefaultTask() { @Input val generateApiDocumentation = project.objects.property() + /** + * Split operations that have multiple response content-types into separate operations + */ + @Optional + @Input + val splitOperation = project.objects.property() + /** * A special-case setting which configures some generators with XML support. In some cases, * this forces json OR xml, so the default here is false. @@ -530,6 +537,10 @@ open class GenerateTask : DefaultTask() { GlobalSettings.setProperty(CodegenConstants.API_TESTS, generateApiTests.get().toString()) } + if (splitOperation.isPresent) { + GlobalSettings.setProperty(CodegenConstants.SPLIT_RESPONSE_TYPES, splitOperation.get().toString()) + } + if (withXml.isPresent) { GlobalSettings.setProperty(CodegenConstants.WITH_XML, withXml.get().toString()) } diff --git a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java index abb3cfe5d424..6e37c82f0648 100644 --- a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java +++ b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java @@ -394,6 +394,12 @@ public class CodeGenMojo extends AbstractMojo { @Parameter(name = "generateApiDocumentation", property = "openapi.generator.maven.plugin.generateApiDocumentation") private Boolean generateApiDocumentation = true; + /** + * Split operations by response content-type when it has multiple values + */ + @Parameter(name = "splitOperation", property = "openapi.generator.maven.plugin.splitOperation") + private Boolean splitOperation = false; + /** * Generate the api documentation */ @@ -650,6 +656,7 @@ public void execute() throws MojoExecutionException { GlobalSettings.setProperty(CodegenConstants.MODEL_DOCS, generateModelDocumentation.toString()); GlobalSettings.setProperty(CodegenConstants.API_TESTS, generateApiTests.toString()); GlobalSettings.setProperty(CodegenConstants.API_DOCS, generateApiDocumentation.toString()); + GlobalSettings.setProperty(CodegenConstants.SPLIT_RESPONSE_TYPES, splitOperation.toString()); GlobalSettings.setProperty(CodegenConstants.WITH_XML, withXml.toString()); if (configOptions != null) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java index 2c0fe6b1b26f..19267fa93a18 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java @@ -46,6 +46,9 @@ public class CodegenConstants { public static final String MODEL_PACKAGE = "modelPackage"; public static final String MODEL_PACKAGE_DESC = "package for generated models"; + public static final String SPLIT_RESPONSE_TYPES = "splitResponseTypes"; + public static final String SPLIT_RESPONSE_TYPES_DESC = "Split operation which has multiple response types into different operations per response type"; + public static final String TEMPLATE_DIR = "templateDir"; public static final String ALLOW_UNICODE_IDENTIFIERS = "allowUnicodeIdentifiers"; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 3c29ddb92023..f80051e1b261 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -3842,12 +3842,20 @@ protected void setNonArrayMapProperty(CodegenProperty property, String type) { } /** - * Override with any special handling of response codes - * * @param responses OAS Operation's responses * @return default method response or null if not found */ - protected ApiResponse findMethodResponse(ApiResponses responses) { + public static ApiResponse findMethodResponse(ApiResponses responses) { + String code = findMethodResponseCode(responses); + return code == null ? null : responses.get(code); + } + + /** + * + * @param responses OAS Operation's responses + * @return value of code that contains method response + */ + public static String findMethodResponseCode(ApiResponses responses) { String code = null; for (String responseCode : responses.keySet()) { if (responseCode.startsWith("2") || responseCode.equals("default")) { @@ -3856,10 +3864,7 @@ protected ApiResponse findMethodResponse(ApiResponses responses) { } } } - if (code == null) { - return null; - } - return responses.get(code); + return code; } /** diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index c4adb5adc8ff..03866a031824 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -25,8 +25,12 @@ import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.MediaType; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.security.*; import io.swagger.v3.oas.models.tags.Tag; import org.apache.commons.io.FilenameUtils; @@ -91,6 +95,7 @@ public class DefaultGenerator implements Generator { private Boolean generateModelTests = null; private Boolean generateModelDocumentation = null; private Boolean generateMetadata = true; + private Boolean splitResponseTypes = false; private String basePath; private String basePathWithoutHost; private String contextPath; @@ -229,6 +234,7 @@ void configureGeneratorProperties() { generateModelDocumentation = GlobalSettings.getProperty(CodegenConstants.MODEL_DOCS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.MODEL_DOCS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.MODEL_DOCS, true); generateApiTests = GlobalSettings.getProperty(CodegenConstants.API_TESTS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.API_TESTS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.API_TESTS, true); generateApiDocumentation = GlobalSettings.getProperty(CodegenConstants.API_DOCS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.API_DOCS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.API_DOCS, true); + splitResponseTypes = GlobalSettings.getProperty(CodegenConstants.SPLIT_RESPONSE_TYPES) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.SPLIT_RESPONSE_TYPES)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.SPLIT_RESPONSE_TYPES, false); // Additional properties added for tests to exclude references in project related files config.additionalProperties().put(CodegenConstants.GENERATE_API_TESTS, generateApiTests); @@ -1067,17 +1073,17 @@ public Map> processPaths(Paths paths) { return ops; } - private void processOperation(String resourcePath, String httpMethod, Operation operation, Map> operations, PathItem path) { - if (operation == null) { + private void processOperation(String resourcePath, String httpMethod, Operation inputOperation, Map> operations, PathItem path) { + if (inputOperation == null) { return; } if (GlobalSettings.getProperty("debugOperations") != null) { - LOGGER.info("processOperation: resourcePath= {}\t;{} {}\n", resourcePath, httpMethod, operation); + LOGGER.info("processOperation: resourcePath= {}\t;{} {}\n", resourcePath, httpMethod, inputOperation); } List tags = new ArrayList<>(); - List tagNames = operation.getTags(); + List tagNames = inputOperation.getTags(); List swaggerTags = openAPI.getTags(); if (tagNames != null) { if (swaggerTags == null) { @@ -1112,8 +1118,8 @@ private void processOperation(String resourcePath, String httpMethod, Operation i'm assuming "location" == "in" */ Set operationParameters = new HashSet<>(); - if (operation.getParameters() != null) { - for (Parameter parameter : operation.getParameters()) { + if (inputOperation.getParameters() != null) { + for (Parameter parameter : inputOperation.getParameters()) { operationParameters.add(generateParameterId(parameter)); } } @@ -1123,51 +1129,126 @@ private void processOperation(String resourcePath, String httpMethod, Operation for (Parameter parameter : path.getParameters()) { //skip propagation if a parameter with the same name is already defined at the operation level if (!operationParameters.contains(generateParameterId(parameter))) { - operation.addParametersItem(parameter); + inputOperation.addParametersItem(parameter); } } } final Map securitySchemes = openAPI.getComponents() != null ? openAPI.getComponents().getSecuritySchemes() : null; final List globalSecurities = openAPI.getSecurity(); - for (Tag tag : tags) { - try { - CodegenOperation codegenOperation = config.fromOperation(resourcePath, httpMethod, operation, path.getServers()); - codegenOperation.tags = new ArrayList<>(tags); - config.addOperationToGroup(config.sanitizeTag(tag.getName()), resourcePath, operation, codegenOperation, operations); - - List securities = operation.getSecurity(); - if (securities != null && securities.isEmpty()) { - continue; - } + for (Operation operation : processMultipleResponseContentTypes(inputOperation)) { + for (Tag tag : tags) { + try { + CodegenOperation codegenOperation = config.fromOperation(resourcePath, httpMethod, operation, path.getServers()); + codegenOperation.tags = new ArrayList<>(tags); + config.addOperationToGroup(config.sanitizeTag(tag.getName()), resourcePath, operation, codegenOperation, operations); - Map authMethods = getAuthMethods(securities, securitySchemes); + List securities = operation.getSecurity(); + if (securities != null && securities.isEmpty()) { + continue; + } - if (authMethods != null && !authMethods.isEmpty()) { - List fullAuthMethods = config.fromSecurity(authMethods); - codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, securities); - codegenOperation.hasAuthMethods = true; - } else { - authMethods = getAuthMethods(globalSecurities, securitySchemes); + Map authMethods = getAuthMethods(securities, securitySchemes); if (authMethods != null && !authMethods.isEmpty()) { List fullAuthMethods = config.fromSecurity(authMethods); - codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, globalSecurities); + codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, securities); codegenOperation.hasAuthMethods = true; + } else { + authMethods = getAuthMethods(globalSecurities, securitySchemes); + + if (authMethods != null && !authMethods.isEmpty()) { + List fullAuthMethods = config.fromSecurity(authMethods); + codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, globalSecurities); + codegenOperation.hasAuthMethods = true; + } } - } - } catch (Exception ex) { - String msg = "Could not process operation:\n" // + } catch (Exception ex) { + String msg = "Could not process operation:\n" // + " Tag: " + tag + "\n"// + " Operation: " + operation.getOperationId() + "\n" // + " Resource: " + httpMethod + " " + resourcePath + "\n"// + " Schemas: " + openAPI.getComponents().getSchemas() + "\n" // + " Exception: " + ex.getMessage(); - throw new RuntimeException(msg, ex); + throw new RuntimeException(msg, ex); + } } } + } + + private List processMultipleResponseContentTypes(Operation operation) { + if (!splitResponseTypes) { + return Collections.singletonList(operation); + } + + ApiResponses apiResponses = operation.getResponses(); + ApiResponse methodResponse = DefaultCodegen.findMethodResponse(apiResponses); + if (methodResponse == null || methodResponse.getContent().size() < 2) { + return Collections.singletonList(operation); + } + + LOGGER.info("Split operation is enabled: operation {} would be split into {} different operations", operation.getOperationId(), methodResponse.getContent().size()); + List operations = new ArrayList<>(); + String methodResponseCodeWithMultipleContentTypes = DefaultCodegen.findMethodResponseCode(apiResponses); + Content methodResponseAllContentTypes = methodResponse.getContent(); + for (String contentTypeName : methodResponseAllContentTypes.keySet()) { + MediaType contentType = methodResponseAllContentTypes.get(contentTypeName); + + Content contentTypeWithSingeValue = new Content(); + contentTypeWithSingeValue.addMediaType(contentTypeName, contentType); + ApiResponse apiResponseWithSingeContentType = copyApiResponse(methodResponse); + apiResponseWithSingeContentType.setContent(contentTypeWithSingeValue); + + ApiResponses singleContentTypeApiResponses = copyApiResponses(apiResponses); + singleContentTypeApiResponses.put(methodResponseCodeWithMultipleContentTypes, apiResponseWithSingeContentType); + + Operation singleContentTypeOperation = copyOperation(operation); + singleContentTypeOperation.setResponses(singleContentTypeApiResponses); + // correct operation name should be handled by final generator + singleContentTypeOperation.setOperationId(operation.getOperationId() + "-" + contentTypeName); + + operations.add(singleContentTypeOperation); + } + + return operations; + } + + private static Operation copyOperation(Operation operation) { + Operation copy = new Operation(); + copy.setTags(operation.getTags()); + copy.setSummary(operation.getSummary()); + copy.setDescription(operation.getDescription()); + copy.setExternalDocs(operation.getExternalDocs()); + copy.setOperationId(operation.getOperationId()); + copy.setParameters(operation.getParameters()); + copy.setRequestBody(operation.getRequestBody()); + copy.setResponses(operation.getResponses()); + copy.setCallbacks(operation.getCallbacks()); + copy.setDeprecated(operation.getDeprecated()); + copy.setSecurity(operation.getSecurity()); + copy.setServers(operation.getServers()); + copy.setExtensions(operation.getExtensions()); + return copy; + } + + private static ApiResponses copyApiResponses(ApiResponses apiResponses) { + ApiResponses copy = new ApiResponses(); + copy.setExtensions(apiResponses.getExtensions()); + apiResponses.forEach((code, value) -> copy.put(code, copyApiResponse(value))); + return copy; + } + + private static ApiResponse copyApiResponse(ApiResponse apiResponse) { + ApiResponse copy = new ApiResponse(); + copy.setDescription(apiResponse.getDescription()); + copy.setHeaders(apiResponse.getHeaders()); + copy.setContent(apiResponse.getContent()); + copy.setLinks(apiResponse.getLinks()); + copy.setExtensions(apiResponse.getExtensions()); + copy.set$ref(apiResponse.get$ref()); + return copy; } private static String generateParameterId(Parameter parameter) { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index 997d2335e3fe..a7d45f9d54bd 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -44,6 +44,7 @@ import java.util.stream.Collectors; import org.assertj.core.api.Assertions; +import org.openapitools.codegen.config.GlobalSettings; import org.openapitools.codegen.java.assertions.JavaFileAssert; import org.openapitools.codegen.CliOption; import org.openapitools.codegen.ClientOptInput; @@ -1380,4 +1381,112 @@ public void shouldSetDefaultValueForMultipleArrayItems() throws IOException { .assertParameterAnnotations() .containsWithNameAndAttributes("RequestParam", ImmutableMap.of("defaultValue", "\"\"")); } + + @Test + public void shouldGenerateSingleOperationWithDifferentReturnTypes() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/bugs/issue_8700.yaml", null, new ParseOptions()).getOpenAPI(); + SpringCodegen codegen = new SpringCodegen(); + codegen.setLibrary(SPRING_BOOT); + codegen.additionalProperties().put(SpringCodegen.USE_TAGS, "true"); + codegen.setOutputDir(output.getAbsolutePath()); + + ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + Map files = generator.opts(input).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("TestControllerApi.java")) + .assertMethod("operationWithDifferentReturnType") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/hal+json\", \"application/json\", \"text/xml\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithSameReturnType") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/hal+json\", \"application/json\", \"text/xml\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithSingleReturnType") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/hal+json\" }")); + } + + @Test + public void shouldSplitOperationsByDifferentReturnTypes() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + + GlobalSettings.setProperty(CodegenConstants.SPLIT_RESPONSE_TYPES, "true"); + + try { + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/bugs/issue_8700.yaml", null, new ParseOptions()).getOpenAPI(); + SpringCodegen codegen = new SpringCodegen(); + codegen.setLibrary(SPRING_BOOT); + codegen.additionalProperties().put(SpringCodegen.USE_TAGS, "true"); + codegen.setOutputDir(output.getAbsolutePath()); + + ClientOptInput input = new ClientOptInput() + .openAPI(openAPI) + .config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + Map files = generator.opts(input).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("TestControllerApi.java")) + .assertMethod("operationWithSameReturnTypeApplicationHaljson") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/hal+json\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithSameReturnTypeApplicationJson") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/json\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithSameReturnTypeTextXml") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"text/xml\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithDifferentReturnTypeApplicationHaljson") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/hal+json\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithDifferentReturnTypeApplicationJson") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/json\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithDifferentReturnTypeTextXml") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"text/xml\" }")) + .toMethod() + .toFileAssert() + .assertMethod("operationWithSingleReturnType") + .hasReturnType("ResponseEntity") + .assertMethodAnnotations() + .containsWithNameAndAttributes("RequestMapping", ImmutableMap.of("produces", "{ \"application/hal+json\" }")); + } finally { + GlobalSettings.setProperty(CodegenConstants.SPLIT_RESPONSE_TYPES, "false"); + } + } } diff --git a/modules/openapi-generator/src/test/resources/bugs/issue_8700.yaml b/modules/openapi-generator/src/test/resources/bugs/issue_8700.yaml new file mode 100644 index 000000000000..5e1a3a06f053 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/bugs/issue_8700.yaml @@ -0,0 +1,107 @@ +openapi: 3.0.2 +info: + title: Petstore - OpenAPI 3.0 + description: Specification to reproduce issues with mediatypes other than common application/json + version: '1.0' +servers: +- url: http://localhost:8080/api/v3 + +paths: + /same-return-type: + get: + tags: + - test-controller + operationId: operationWithSameReturnType + responses: + 200: + description: OK + content: + application/hal+json: + schema: + $ref: '#/components/schemas/ResponseObject' + application/json: + schema: + $ref: '#/components/schemas/ResponseObject' + text/xml: + schema: + $ref: '#/components/schemas/ResponseObject' + 401: + description: Unauthorized + content: {} + 403: + description: Forbidden + content: {} + 404: + description: Not Found + content: {} + /different-return-type: + get: + tags: + - test-controller + operationId: operationWithDifferentReturnType + responses: + 200: + description: OK + content: + application/hal+json: + schema: + $ref: '#/components/schemas/ResponseObject' + application/json: + schema: + type: string + text/xml: + schema: + type: string + format: binary + 401: + description: Unauthorized + content: { } + 403: + description: Forbidden + content: { } + 404: + description: Not Found + content: { } + /single-return-type: + get: + tags: + - test-controller + operationId: operationWithSingleReturnType + responses: + 200: + description: OK + content: + application/hal+json: + schema: + $ref: '#/components/schemas/ResponseObject' + 401: + description: Unauthorized + content: { } + 403: + description: Forbidden + content: { } + 404: + description: Not Found + content: { } + +components: + schemas: + ResponseObject: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + description: 'Id' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + example: available \ No newline at end of file