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 02f6f99f6130..f19cffc06061 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", @@ -371,6 +376,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 03cff61a0027..c78b3f226cdd 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 @@ -138,6 +138,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 6fbb11d45d2a..c37dbb5cf4f0 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 @@ -295,6 +295,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. @@ -350,6 +355,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 2adddbdfcf8f..92ae99aaf7a2 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 @@ -423,6 +423,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. @@ -546,6 +553,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 44e49ca5211d..0f84f2e36e2c 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 @@ -406,6 +406,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 */ @@ -662,6 +668,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 1efd6b2502ff..9153eb59d7bb 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 51d3627317cc..45343d0dd446 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 @@ -3951,12 +3951,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")) { @@ -3965,10 +3973,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 f8dbd5361e34..c416c4fa5b19 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); @@ -1087,17 +1093,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) { @@ -1132,8 +1138,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)); } } @@ -1143,51 +1149,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 ac058ecd2ba8..ce13987b4591 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; @@ -654,7 +655,7 @@ public void testMultipartCloud() throws IOException { "List files", "MultipartFile file"); } - + @Test public void testRequestMappingAnnotation() throws IOException { final SpringCodegen codegen = new SpringCodegen(); @@ -1499,4 +1500,112 @@ public void shouldGenerateDiscriminatorFromAllOfWhenUsingLegacyDiscriminatorBeha "})"; assertFileContains(Paths.get(output.getAbsolutePath() + "/src/main/java/org/openapitools/model/Pet.java"), jsonTypeInfo, jsonSubType); } + + @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