diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index fedf18da115..d9900d22664 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -669,7 +669,12 @@ public class SpringCodegen extends AbstractJavaCodegen additionalProperties.put(RESPONSE_WRAPPER, "CompletableFuture"); } if (reactive) { - additionalProperties.put(RESPONSE_WRAPPER, "Mono"); + // The response wrapper when Reactive is enabled must depend on the return type: + // Flux when X is an array + // Mono otherwise + // But there are corner cases when also using response entity. + // When reactive is enabled, all this is managed in the mustache templates. + additionalProperties.put(RESPONSE_WRAPPER, ""); } // Some well-known Spring or Spring-Cloud response wrappers diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-http-interface/api.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-http-interface/api.mustache index fecd5259ec1..33a1ec0b183 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-http-interface/api.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-http-interface/api.mustache @@ -57,7 +57,7 @@ public interface {{classname}} { accept = "{{{vendorExtensions.x-accepts}}}"{{/vendorExtensions.x-accepts}}{{#vendorExtensions.x-content-type}}, contentType = "{{{vendorExtensions.x-content-type}}}"{{/vendorExtensions.x-content-type}} ) - {{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {{operationId}}( + {{>responseType}} {{operationId}}( {{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{>cookieParams}}{{^-last}}, {{/-last}}{{/allParams}} ){{#unhandledException}} throws Exception{{/unhandledException}}; diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/methodBody.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/methodBody.mustache index df3c53532a1..bbbc66de397 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/methodBody.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/methodBody.mustache @@ -50,7 +50,7 @@ Mono result = Mono.empty(); {{^examples}} exchange.getResponse().setStatusCode({{#returnSuccessCode}}HttpStatus.OK{{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}); {{/examples}} - return result{{#allParams}}{{#isBodyParam}}{{^isArray}}{{#paramName}}.then({{.}}){{/paramName}}{{/isArray}}{{#isArray}}{{#paramName}}.thenMany({{.}}){{/paramName}}{{/isArray}}{{/isBodyParam}}{{/allParams}}.then(Mono.empty()); + return result{{#allParams}}{{#isBodyParam}}{{^isArray}}{{#paramName}}.then({{.}}){{/paramName}}{{/isArray}}{{#isArray}}{{#paramName}}.thenMany({{.}}){{/paramName}}{{/isArray}}{{/isBodyParam}}{{/allParams}}{{#isArray}}{{#useResponseEntity}}.then(Mono.empty()){{/useResponseEntity}}{{^useResponseEntity}}.thenMany(Flux.empty()){{/useResponseEntity}}{{/isArray}}{{^isArray}}.then(Mono.empty()){{/isArray}}; {{/vendorExtensions.x-sse}} {{#vendorExtensions.x-sse}} exchange.getResponse().setStatusCode({{#returnSuccessCode}}HttpStatus.valueOf({{{statusCode}}}){{/returnSuccessCode}}{{^returnSuccessCode}}HttpStatus.NOT_IMPLEMENTED{{/returnSuccessCode}}); diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/responseType.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/responseType.mustache index 4bea517eed6..a25da6310b5 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/responseType.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/responseType.mustache @@ -1 +1 @@ -{{^vendorExtensions.x-sse}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}}{{/vendorExtensions.x-sse}}{{#vendorExtensions.x-sse}}{{>returnTypes}}{{/vendorExtensions.x-sse}} \ No newline at end of file +{{^vendorExtensions.x-sse}}{{#reactive}}{{#useResponseEntity}}MonoreturnTypes}}{{#isArray}}>{{/isArray}}>>{{/useResponseEntity}}{{^useResponseEntity}}{{#isArray}}Flux{{/isArray}}{{^isArray}}Mono{{/isArray}}<{{>returnTypes}}>{{/useResponseEntity}}{{/reactive}}{{^reactive}}{{#responseWrapper}}{{.}}<{{/responseWrapper}}{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{#responseWrapper}}>{{/responseWrapper}}{{/reactive}}{{/vendorExtensions.x-sse}}{{#vendorExtensions.x-sse}}{{#isArray}}Flux{{/isArray}}{{^isArray}}Mono{{/isArray}}<{{>returnTypes}}>{{/vendorExtensions.x-sse}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/returnTypes.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/returnTypes.mustache index 3e29bdcf7b7..70bad00b884 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/returnTypes.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/returnTypes.mustache @@ -1 +1 @@ -{{#isMap}}Map{{/isMap}}{{#isArray}}{{#reactive}}Flux{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{#useResponseEntity}}{{{returnType}}}{{/useResponseEntity}}{{^useResponseEntity}}{{#isDelegate}}{{#isVoid}}{{#responseWrapper}}{{{returnType}}}{{/responseWrapper}}{{^responseWrapper}}void{{/responseWrapper}}{{/isVoid}}{{^isVoid}}{{{returnType}}}{{/isVoid}}{{/isDelegate}}{{^isDelegate}}{{#async}}{{{returnType}}}{{/async}}{{^async}}{{#isVoid}}{{#responseWrapper}}{{{returnType}}}{{/responseWrapper}}{{^responseWrapper}}void{{/responseWrapper}}{{/isVoid}}{{^isVoid}}{{{returnType}}}{{/isVoid}}{{/async}}{{/isDelegate}}{{/useResponseEntity}}{{/returnContainer}} \ No newline at end of file +{{#isMap}}Map{{/isMap}}{{#isArray}}{{#reactive}}{{{returnType}}}{{/reactive}}{{^reactive}}{{{returnContainer}}}<{{{returnType}}}>{{/reactive}}{{/isArray}}{{^returnContainer}}{{#useResponseEntity}}{{{returnType}}}{{/useResponseEntity}}{{^useResponseEntity}}{{#isDelegate}}{{#isVoid}}{{#responseWrapper}}{{{returnType}}}{{/responseWrapper}}{{^responseWrapper}}void{{/responseWrapper}}{{/isVoid}}{{^isVoid}}{{{returnType}}}{{/isVoid}}{{/isDelegate}}{{^isDelegate}}{{#async}}{{{returnType}}}{{/async}}{{^async}}{{#isVoid}}{{#responseWrapper}}{{{returnType}}}{{/responseWrapper}}{{^responseWrapper}}void{{/responseWrapper}}{{/isVoid}}{{^isVoid}}{{{returnType}}}{{/isVoid}}{{/async}}{{/isDelegate}}{{/useResponseEntity}}{{/returnContainer}} \ No newline at end of file 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 edf1c088aa1..c88416c2259 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 @@ -541,14 +541,19 @@ public class SpringCodegenTest { } @Test - public void springcloudWithAsyncAndJava8HasResponseWrapperCompletableFuture() { - final SpringCodegen codegen = new SpringCodegen(); - codegen.additionalProperties().put(SpringCodegen.ASYNC, true); - codegen.additionalProperties().put(CodegenConstants.LIBRARY, "spring-cloud"); - codegen.processOpts(); + public void springcloudWithAsyncAndJava8HasResponseWrapperCompletableFuture() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put(SpringCodegen.ASYNC, "true"); + additionalProperties.put(CodegenConstants.LIBRARY, "spring-cloud"); + additionalProperties.put(CodegenConstants.MODEL_TESTS, "false"); + additionalProperties.put(CodegenConstants.MODEL_DOCS, "false"); + additionalProperties.put(CodegenConstants.APIS, "true"); + additionalProperties.put(CodegenConstants.SUPPORTING_FILES, "false"); - Assert.assertEquals(codegen.additionalProperties().get("jdk8-default-interface"), false); - Assert.assertEquals(codegen.additionalProperties().get(RESPONSE_WRAPPER), "CompletableFuture"); + Map files = generateFromContract("src/test/resources/3_0/petstore.yaml", SPRING_BOOT, additionalProperties); + + assertFileContains(files.get("PetApi.java").toPath(), "CompletableFuture> deletePet"); + assertFileNotContains(files.get("PetApi.java").toPath(), "default CompletableFuture> deletePet"); } @Test @@ -1281,6 +1286,50 @@ public class SpringCodegenTest { assertFileNotContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/SomeApiDelegate.java"), "Mono"); } + @Test + public void reactiveArrayShouldBeWrappedInFluxWithoutMono() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put(SpringCodegen.DELEGATE_PATTERN, "false"); + additionalProperties.put(SpringCodegen.REACTIVE, "true"); + additionalProperties.put(SpringCodegen.USE_RESPONSE_ENTITY, "false"); + additionalProperties.put(SpringCodegen.USE_SPRING_BOOT3, "true"); + additionalProperties.put(CodegenConstants.MODEL_TESTS, "false"); + additionalProperties.put(CodegenConstants.MODEL_DOCS, "false"); + additionalProperties.put(CodegenConstants.APIS, "true"); + additionalProperties.put(CodegenConstants.SUPPORTING_FILES, "false"); + Map files = generateFromContract("src/test/resources/3_0/petstore.yaml", SPRING_BOOT, additionalProperties); + + JavaFileAssert + .assertThat(files.get("PetApi.java")) + .assertMethod("addPet").hasReturnType("Mono") + .toFileAssert() + .assertMethod("findPetsByStatus").hasReturnType("Flux") + .toFileAssert() + .assertMethod("deletePet").hasReturnType("Mono"); + } + + @Test + public void reactiveArrayShouldBeWrappedInMonoFluxWhenUsingResponseEntity() throws IOException { + Map additionalProperties = new HashMap<>(); + additionalProperties.put(SpringCodegen.DELEGATE_PATTERN, "false"); + additionalProperties.put(SpringCodegen.REACTIVE, "true"); + additionalProperties.put(SpringCodegen.USE_RESPONSE_ENTITY, "true"); + additionalProperties.put(SpringCodegen.USE_SPRING_BOOT3, "true"); + additionalProperties.put(CodegenConstants.MODEL_TESTS, "false"); + additionalProperties.put(CodegenConstants.MODEL_DOCS, "false"); + additionalProperties.put(CodegenConstants.APIS, "true"); + additionalProperties.put(CodegenConstants.SUPPORTING_FILES, "false"); + Map files = generateFromContract("src/test/resources/3_0/petstore.yaml", SPRING_BOOT, additionalProperties); + + JavaFileAssert + .assertThat(files.get("PetApi.java")) + .assertMethod("addPet").hasReturnType("Mono>") + .toFileAssert() + .assertMethod("findPetsByStatus").hasReturnType("Mono>>") + .toFileAssert() + .assertMethod("deletePet").hasReturnType("Mono>"); + } + @Test public void shouldGenerateValidCodeForReactiveControllerWithoutParams_issue14907() throws IOException { File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); diff --git a/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApi.java b/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApi.java index 69aef765873..2dc189d6983 100644 --- a/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApi.java +++ b/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApi.java @@ -146,7 +146,7 @@ public interface PetApi { ) @ResponseStatus(HttpStatus.OK) - default Mono> findPetsByStatus( + default Flux findPetsByStatus( @NotNull @ApiParam(value = "Status values that need to be considered for filter", required = true, allowableValues = "available, pending, sold") @Valid @RequestParam(value = "status", required = true) List status, @ApiIgnore final ServerWebExchange exchange ) { @@ -189,7 +189,7 @@ public interface PetApi { ) @ResponseStatus(HttpStatus.OK) - default Mono> findPetsByTags( + default Flux findPetsByTags( @NotNull @ApiParam(value = "Tags to filter by", required = true) @Valid @RequestParam(value = "tags", required = true) Set tags, @ApiIgnore final ServerWebExchange exchange ) { diff --git a/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApiDelegate.java b/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApiDelegate.java index ade70ed2956..0a43161c790 100644 --- a/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApiDelegate.java +++ b/samples/server/petstore/springboot-reactive-noResponseEntity/src/main/java/org/openapitools/api/PetApiDelegate.java @@ -76,7 +76,7 @@ public interface PetApiDelegate { * or Invalid status value (status code 400) * @see PetApi#findPetsByStatus */ - default Mono> findPetsByStatus(List status, + default Flux findPetsByStatus(List status, ServerWebExchange exchange) { Mono result = Mono.empty(); exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED); @@ -92,7 +92,7 @@ public interface PetApiDelegate { break; } } - return result.then(Mono.empty()); + return result.thenMany(Flux.empty()); } @@ -107,7 +107,7 @@ public interface PetApiDelegate { * @see PetApi#findPetsByTags */ @Deprecated - default Mono> findPetsByTags(Set tags, + default Flux findPetsByTags(Set tags, ServerWebExchange exchange) { Mono result = Mono.empty(); exchange.getResponse().setStatusCode(HttpStatus.NOT_IMPLEMENTED); @@ -123,7 +123,7 @@ public interface PetApiDelegate { break; } } - return result.then(Mono.empty()); + return result.thenMany(Flux.empty()); }