diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java index c47f30e6a367..9f98815c7b5e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java @@ -1350,6 +1350,18 @@ protected void doDataTypeAssignment(final String returnType, DataTypeAssigner da dataTypeAssigner.setReturnType(returnType.substring("kotlin.collections.MutableList<".length(), end).trim()); dataTypeAssigner.setReturnContainer("List"); } + } else if (returnType.startsWith("kotlin.collections.MutableSet")) { + int end = returnType.lastIndexOf(">"); + if (end > 0) { + dataTypeAssigner.setReturnType(returnType.substring("kotlin.collections.MutableSet<".length(), end).trim()); + dataTypeAssigner.setReturnContainer("Set"); + } + } else if (returnType.startsWith("kotlin.collections.Set")) { + int end = returnType.lastIndexOf(">"); + if (end > 0) { + dataTypeAssigner.setReturnType(returnType.substring("kotlin.collections.Set<".length(), end).trim()); + dataTypeAssigner.setReturnContainer("Set"); + } } else if (returnType.startsWith("kotlin.collections.Map")) { int end = returnType.lastIndexOf(">"); if (end > 0) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java index 619d44868dc4..4227feda5942 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java @@ -1622,6 +1622,19 @@ public void setReturnContainer(final String returnContainer) { } }); + // Flow is broken — StringDecoder intercepts String and returns the entire + // JSON array as a single blob instead of using Jackson. Fix by switching + // array-of-string operations to List (with suspend). + // See https://github.com/spring-projects/spring-framework/issues/22662 + // Note: check operation.returnType (set by doDataTypeAssignment) which holds the + // unwrapped inner type, e.g. "kotlin.String" for List arrays. + // The declarative-http-interface library forces useFlowForArrayReturnType=false, + // so this condition only fires for the spring-boot coroutines path. + if (reactive && useFlowForArrayReturnType + && operation.isArray && "kotlin.String".equals(operation.returnType)) { + operation.vendorExtensions.put("x-reactive-array-string-return", true); + } + // Generate sealed response interface metadata if enabled if (useSealedResponseInterfaces && responses != null && !responses.isEmpty()) { // Generate sealed interface name from operation ID diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache index 5b9f530282d0..448cf0c8726e 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache @@ -110,7 +110,7 @@ class {{classname}}Controller({{#serviceInterface}}@Autowired(required = true) v produces = [{{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}}]{{/hasProduces}}{{#hasConsumes}}, consumes = [{{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}}]{{/hasConsumes}}{{/singleContentTypes}} ) - {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}} + {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{#vendorExtensions.x-reactive-array-string-return}}suspend {{/vendorExtensions.x-reactive-array-string-return}}{{^vendorExtensions.x-reactive-array-string-return}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/vendorExtensions.x-reactive-array-string-return}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}} {{>queryParams}}{{>pathParams}}{{>headerParams}}{{>cookieParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache index 9dbeecc9a1a9..e6efd9948039 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache @@ -32,7 +32,7 @@ interface {{classname}}Delegate { /** * @see {{classname}}#{{operationId}} */ - {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}{{#isBodyParam}}Flow<{{{baseType}}}>{{/isBodyParam}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{/isArray}}{{/reactive}}{{^-last}}, + {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{#vendorExtensions.x-reactive-array-string-return}}suspend {{/vendorExtensions.x-reactive-array-string-return}}{{^vendorExtensions.x-reactive-array-string-return}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/vendorExtensions.x-reactive-array-string-return}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}{{#isBodyParam}}Flow<{{{baseType}}}>{{/isBodyParam}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{/isArray}}{{/reactive}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache index 82e091bd1aca..3da7f3319cc3 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache @@ -125,7 +125,7 @@ interface {{classname}} { produces = [{{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}}]{{/hasProduces}}{{#hasConsumes}}, consumes = [{{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}}]{{/hasConsumes}}{{/singleContentTypes}} ) - {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}} + {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{#vendorExtensions.x-reactive-array-string-return}}suspend {{/vendorExtensions.x-reactive-array-string-return}}{{^vendorExtensions.x-reactive-array-string-return}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/vendorExtensions.x-reactive-array-string-return}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}} {{>queryParams}}{{>pathParams}}{{>headerParams}}{{>cookieParams}}{{>bodyParams}}{{>formParams}}{{^-last}},{{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/apiInterface.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/apiInterface.mustache index 53ce2730aa8b..2e7a59289cb4 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/apiInterface.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/apiInterface.mustache @@ -43,7 +43,6 @@ import {{javaxPackage}}.validation.constraints.* {{/useBeanValidation}} {{#reactiveModeReactor}} -import reactor.core.publisher.Flux import reactor.core.publisher.Mono {{/reactiveModeReactor}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache index 4f36af09bfba..f4bb3da4fa13 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/libraries/spring-declarative-http-interface/httpInterfaceReturnTypes.mustache @@ -6,7 +6,7 @@ {{#isArray}} {{! array handle reactive - reactor with/without ResponseEntity wrapper}} {{#reactiveModeReactor}} -{{#useResponseEntity}}Mono{{#useResponseEntity}}>>{{/useResponseEntity}} +Mono<{{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{{returnContainer}}}<{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{{returnType}}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{{returnType}}}{{/useSealedResponseInterfaces}}>{{#useResponseEntity}}>{{/useResponseEntity}}> {{/reactiveModeReactor}} {{! array handle reactive - coroutines with/without ResponseEntity wrapper}} {{#reactiveModeCoroutines}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/returnTypes.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/returnTypes.mustache index 612aa9ec0599..a3a7a14b00ff 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/returnTypes.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/returnTypes.mustache @@ -1 +1 @@ -{{#isMap}}Map{{/isMap}}{{#isArray}}{{#reactive}}{{#useFlowForArrayReturnType}}Flow{{/useFlowForArrayReturnType}}{{^useFlowForArrayReturnType}}{{{returnContainer}}}{{/useFlowForArrayReturnType}}{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{{returnType}}}{{/returnContainer}} \ No newline at end of file +{{#isMap}}Map{{/isMap}}{{#isArray}}{{#reactive}}{{#vendorExtensions.x-reactive-array-string-return}}{{{returnContainer}}}{{/vendorExtensions.x-reactive-array-string-return}}{{^vendorExtensions.x-reactive-array-string-return}}{{#useFlowForArrayReturnType}}Flow{{/useFlowForArrayReturnType}}{{^useFlowForArrayReturnType}}{{{returnContainer}}}{{/useFlowForArrayReturnType}}{{/vendorExtensions.x-reactive-array-string-return}}{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{{returnType}}}{{/returnContainer}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/service.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/service.mustache index c6372d1bf827..e8972a7afd51 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/service.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/service.mustache @@ -33,7 +33,7 @@ interface {{classname}}Service { {{#isDeprecated}} @Deprecated(message="Operation is deprecated") {{/isDeprecated}} - {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}): {{>returnTypes}} + {{#suspendFunctions}}suspend {{/suspendFunctions}}{{^suspendFunctions}}{{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{#vendorExtensions.x-reactive-array-string-return}}suspend {{/vendorExtensions.x-reactive-array-string-return}}{{^vendorExtensions.x-reactive-array-string-return}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/vendorExtensions.x-reactive-array-string-return}}{{/isArray}}{{/reactive}}{{/suspendFunctions}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}): {{>returnTypes}} {{/operation}} } {{/operations}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/serviceImpl.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/serviceImpl.mustache index 181bfa52991c..18d61ae1bb23 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-spring/serviceImpl.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-spring/serviceImpl.mustache @@ -11,7 +11,7 @@ import org.springframework.stereotype.Service class {{classname}}ServiceImpl : {{classname}}Service { {{#operation}} - override {{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}): {{>returnTypes}} { + override {{#reactive}}{{^isArray}}suspend {{/isArray}}{{#isArray}}{{#vendorExtensions.x-reactive-array-string-return}}suspend {{/vendorExtensions.x-reactive-array-string-return}}{{^vendorExtensions.x-reactive-array-string-return}}{{^useFlowForArrayReturnType}}suspend {{/useFlowForArrayReturnType}}{{/vendorExtensions.x-reactive-array-string-return}}{{/isArray}}{{/reactive}}fun {{operationId}}({{#allParams}}{{{paramName}}}: {{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{^reactive}}{{>optionalDataType}}{{/reactive}}{{#reactive}}{{^isArray}}{{>optionalDataType}}{{/isArray}}{{#isArray}}Flow<{{{baseType}}}>{{/isArray}}{{/reactive}}{{/isBodyParam}}{{^-last}}, {{/-last}}{{/allParams}}): {{>returnTypes}} { TODO("Implement me") } {{/operation}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java index c99cf7a0aae1..018659c1cdf4 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java @@ -373,13 +373,13 @@ public void delegateReactiveWithTags() throws Exception { "ApiUtil"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV2Api.kt"), - "import kotlinx.coroutines.flow.Flow", "ResponseEntity>"); + "import kotlinx.coroutines.flow.Flow", "ResponseEntity>"); assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV2Api.kt"), "exchange"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV2ApiDelegate.kt"), - "import kotlinx.coroutines.flow.Flow", "ResponseEntity>"); + "import kotlinx.coroutines.flow.Flow", "suspend fun", "ResponseEntity>"); assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV2ApiDelegate.kt"), - "suspend fun", "ApiUtil"); + "ApiUtil"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV3Api.kt"), "import kotlinx.coroutines.flow.Flow", "requestBody: Flow"); @@ -1294,8 +1294,7 @@ public void generateHttpInterfaceReactiveWithReactorResponseEntity() throws Exce Path path = Paths.get(outputPath + "/src/main/kotlin/org/openapitools/api/StoreApi.kt"); assertFileContains( path, - "import reactor.core.publisher.Flux\n" - + "import reactor.core.publisher.Mono", + "import reactor.core.publisher.Mono", " @HttpExchange(\n" + " // \"/store/inventory\"\n" + " url = PATH_GET_INVENTORY,\n" @@ -1408,8 +1407,7 @@ public void generateHttpInterfaceReactiveWithReactor() throws Exception { Path path = Paths.get(outputPath + "/src/main/kotlin/org/openapitools/api/StoreApi.kt"); assertFileContains( path, - "import reactor.core.publisher.Flux\n" - + "import reactor.core.publisher.Mono", + "import reactor.core.publisher.Mono", " fun getInventory(\n" + " ): Mono>", " fun deleteOrder(\n" @@ -3144,21 +3142,21 @@ public void reactiveWithFlow() throws Exception { ); assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"), - "List"); + "Flow"); - assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"), - "List"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"), + "List"); + assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"), "Flow"); - assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"), - "List"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"), + "List"); + assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"), "Flow"); - assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"), - "List"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"), + "List"); + assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"), "Flow"); } @@ -3190,21 +3188,21 @@ public void reactiveWithDefaultValueFlow() throws Exception { ); assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"), - "List"); + "Flow"); - assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"), - "List"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"), + "List"); + assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"), "Flow"); - assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"), - "List"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"), + "List"); + assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"), "Flow"); - assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"), - "List"); assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"), + "List"); + assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"), "Flow"); } @@ -4167,6 +4165,88 @@ public void springPaginatedDelegateCallPassesPageable() throws Exception { } } + @Test(description = "reactive spring-boot: array-of-string returns List with suspend, not Flow (issue #22662)") + public void reactiveArrayOfStringReturnsListNotFlow() throws Exception { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, true); + codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, true); + + List files = new DefaultGenerator() + .opts(new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/bugs/issue_7118.yaml")) + .config(codegen)) + .generate(); + + Path apiPath = files.stream() + .filter(f -> f.getName().equals("UsersApi.kt")) + .findFirst() + .orElseThrow() + .toPath(); + + assertFileContains(apiPath, "suspend fun", "List", "Set"); + // neither the list nor the uniqueItems (Set) operation must leak Flow<...> or a raw/nested container + assertFileNotContains(apiPath, "Flow", "Flow files = new DefaultGenerator() + .opts(new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/bugs/issue_7118.yaml")) + .config(codegen)) + .generate(); + + Path apiPath = files.stream() + .filter(f -> f.getName().equals("UsersApi.kt")) + .findFirst() + .orElseThrow() + .toPath(); + + assertFileContains(apiPath, "Mono>", "Mono>"); + assertFileNotContains(apiPath, "Flux", "import reactor.core.publisher.Flux", + "kotlin.collections.Set<", "Mono files = new DefaultGenerator() + .opts(new ClientOptInput() + .openAPI(TestUtils.parseSpec("src/test/resources/bugs/issue_7118.yaml")) + .config(codegen)) + .generate(); + + Path apiPath = files.stream() + .filter(f -> f.getName().equals("UsersApi.kt")) + .findFirst() + .orElseThrow() + .toPath(); + + assertFileContains(apiPath, "Mono>>", "Mono>>"); + assertFileNotContains(apiPath, "Flux", "import reactor.core.publisher.Flux", + "kotlin.collections.Set<", "Mono generateFromContract(String url) throws IOException { return generateFromContract(url, new HashMap<>(), new HashMap<>()); } diff --git a/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/PetApi.kt b/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/PetApi.kt index 155e5e314ca7..e7b8463c72ad 100644 --- a/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/PetApi.kt +++ b/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/PetApi.kt @@ -21,7 +21,6 @@ import org.springframework.validation.annotation.Validated import jakarta.validation.Valid import jakarta.validation.constraints.* -import reactor.core.publisher.Flux import reactor.core.publisher.Mono import kotlin.collections.List @@ -58,7 +57,7 @@ interface PetApi { ) fun findPetsByStatus( @NotNull @Parameter(description = "Status values that need to be considered for filter", required = true, schema = Schema(allowableValues = ["available", "pending", "sold"])) @Valid @RequestParam(value = "status", required = true) status: kotlin.collections.List - ): Mono>> + ): Mono>> @HttpExchange( @@ -68,7 +67,7 @@ interface PetApi { ) fun findPetsByTags( @NotNull @Parameter(description = "Tags to filter by", required = true) @Valid @RequestParam(value = "tags", required = true) tags: kotlin.collections.List - ): Mono>> + ): Mono>> @HttpExchange( diff --git a/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/StoreApi.kt b/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/StoreApi.kt index d16ddcb5cbe3..fcbeebbe7764 100644 --- a/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/StoreApi.kt +++ b/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/StoreApi.kt @@ -20,7 +20,6 @@ import org.springframework.validation.annotation.Validated import jakarta.validation.Valid import jakarta.validation.constraints.* -import reactor.core.publisher.Flux import reactor.core.publisher.Mono import kotlin.collections.List diff --git a/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/UserApi.kt b/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/UserApi.kt index db999b112dbe..5c6814419e1e 100644 --- a/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/UserApi.kt +++ b/samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped/src/main/kotlin/org/openapitools/api/UserApi.kt @@ -20,7 +20,6 @@ import org.springframework.validation.annotation.Validated import jakarta.validation.Valid import jakarta.validation.constraints.* -import reactor.core.publisher.Flux import reactor.core.publisher.Mono import kotlin.collections.List