Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[kotlin-spring] Adds useFlowForArrayReturnType option for reactive mode (#16130) #20409

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/samples-kotlin-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
- samples/server/petstore/kotlin-springboot-delegate
- samples/server/petstore/kotlin-springboot-modelMutable
- samples/server/petstore/kotlin-springboot-reactive
- samples/server/petstore/kotlin-springboot-reactive-with-flow
- samples/server/petstore/kotlin-springboot-reactive-without-flow
- samples/server/petstore/kotlin-springboot-source-swagger1
- samples/server/petstore/kotlin-springboot-source-swagger2
- samples/server/petstore/kotlin-springboot-springfox
Expand Down
13 changes: 13 additions & 0 deletions bin/configs/kotlin-spring-boot-reactive-with-flow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-springboot-reactive-with-flow
library: spring-boot
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
additionalProperties:
documentationProvider: springdoc
annotationLibrary: swagger2
useSwaggerUI: "true"
serviceImplementation: "true"
reactive: "true"
beanValidations: "true"
useFlowForArrayReturnType: "true"
13 changes: 13 additions & 0 deletions bin/configs/kotlin-spring-boot-reactive-without-flow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-springboot-reactive-without-flow
library: spring-boot
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
additionalProperties:
documentationProvider: springdoc
annotationLibrary: swagger2
useSwaggerUI: "true"
serviceImplementation: "true"
reactive: "true"
beanValidations: "true"
useFlowForArrayReturnType: "false"
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|title|server title name or client service name| |OpenAPI Kotlin Spring|
|useBeanValidation|Use BeanValidation API annotations to validate data types| |true|
|useFeignClientUrl|Whether to generate Feign client with url parameter.| |true|
|useFlowForArrayReturnType|Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.| |true|
|useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false|
|useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true|
|useTags|Whether to use tags for creating interface and controller class names| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String BEAN_QUALIFIERS = "beanQualifiers";

public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
public static final String USE_FLOW_FOR_ARRAY_RETURN_TYPE = "useFlowForArrayReturnType";
public static final String REQUEST_MAPPING_OPTION = "requestMappingMode";
public static final String USE_REQUEST_MAPPING_ON_CONTROLLER = "useRequestMappingOnController";
public static final String USE_REQUEST_MAPPING_ON_INTERFACE = "useRequestMappingOnInterface";
Expand Down Expand Up @@ -145,6 +146,8 @@ public String getDescription() {
@Setter private boolean serviceImplementation = false;
@Getter @Setter
private boolean reactive = false;
@Getter @Setter
private boolean useFlowForArrayReturnType = true;
@Setter private boolean interfaceOnly = false;
@Setter protected boolean useFeignClientUrl = true;
@Setter protected boolean useFeignClient = false;
Expand Down Expand Up @@ -235,6 +238,7 @@ public KotlinSpringServerCodegen() {
"@RestController annotations. May be used to prevent bean names clash if multiple generated libraries" +
" (contexts) added to single project.", beanQualifiers);
addSwitch(USE_SPRING_BOOT3, "Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.", useSpringBoot3);
addSwitch(USE_FLOW_FOR_ARRAY_RETURN_TYPE, "Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.", useFlowForArrayReturnType);
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
Expand Down Expand Up @@ -527,10 +531,15 @@ public void processOpts() {
this.setReactive(convertPropertyToBoolean(REACTIVE));
// spring webflux doesn't support @ControllerAdvice
this.setExceptionHandler(false);

if (additionalProperties.containsKey(USE_FLOW_FOR_ARRAY_RETURN_TYPE)) {
this.setUseFlowForArrayReturnType(convertPropertyToBoolean(USE_FLOW_FOR_ARRAY_RETURN_TYPE));
}
}
}
writePropertyBack(REACTIVE, reactive);
writePropertyBack(EXCEPTION_HANDLER, exceptionHandler);
writePropertyBack(USE_FLOW_FOR_ARRAY_RETURN_TYPE, useFlowForArrayReturnType);

if (additionalProperties.containsKey(BEAN_QUALIFIERS) && library.equals(SPRING_BOOT)) {
this.setBeanQualifiers(convertPropertyToBoolean(BEAN_QUALIFIERS));
Expand Down Expand Up @@ -685,7 +694,7 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("buildGradleKts.mustache", "build.gradle.kts"));
}
supportingFiles.add(new SupportingFile("settingsGradle.mustache", "settings.gradle"));

String gradleWrapperPackage = "gradle.wrapper";
supportingFiles.add(new SupportingFile("gradlew.mustache", "", "gradlew"));
supportingFiles.add(new SupportingFile("gradlew.bat.mustache", "", "gradlew.bat"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{{#isMap}}Map<String, {{{returnType}}}>{{/isMap}}{{#isArray}}{{#reactive}}Flow{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{{returnType}}}{{/returnContainer}}
{{#isMap}}Map<String, {{{returnType}}}>{{/isMap}}{{#isArray}}{{#reactive}}{{#useFlowForArrayReturnType}}Flow{{/useFlowForArrayReturnType}}{{^useFlowForArrayReturnType}}{{{returnContainer}}}{{/useFlowForArrayReturnType}}{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{{returnType}}}{{/returnContainer}}
Original file line number Diff line number Diff line change
Expand Up @@ -911,4 +911,230 @@ public void generateSerializableModel() throws Exception {
"private const val serialVersionUID: kotlin.Long = 1"
);
}

@Test
public void reactiveWithoutFlow() 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, false);
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);

List<File> files = new DefaultGenerator()
.opts(
new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
.config(codegen)
)
.generate();

Assertions.assertThat(files).contains(
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
);

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"Flow<kotlin.String>");
}

@Test
public void reactiveWithFlow() 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.USE_TAGS, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);

List<File> files = new DefaultGenerator()
.opts(
new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
.config(codegen)
)
.generate();

Assertions.assertThat(files).contains(
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
);

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
"List<kotlin.String>");

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"List<kotlin.String>");
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"Flow<kotlin.String>");

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"List<kotlin.String>");
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"Flow<kotlin.String>");

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"List<kotlin.String>");
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"Flow<kotlin.String>");
}

@Test
public void reactiveWithDefaultValueFlow() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, true);
// should use default 'true' instead
// codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);

List<File> files = new DefaultGenerator()
.opts(
new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
.config(codegen)
)
.generate();

Assertions.assertThat(files).contains(
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
);

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
"List<kotlin.String>");

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"List<kotlin.String>");
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"Flow<kotlin.String>");

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"List<kotlin.String>");
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"Flow<kotlin.String>");

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"List<kotlin.String>");
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"Flow<kotlin.String>");
}

@Test
public void nonReactiveWithoutFlow() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, false);
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, false);
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);

List<File> files = new DefaultGenerator()
.opts(
new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
.config(codegen)
)
.generate();

Assertions.assertThat(files).contains(
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
);

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"Flow<kotlin.String>");
}

@Test
public void nonReactiveWithFlow() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, false);
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);

List<File> files = new DefaultGenerator()
.opts(
new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
.config(codegen)
)
.generate();

Assertions.assertThat(files).contains(
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
);

assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
"Flow<kotlin.String>");

assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"List<kotlin.String>");
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
"Flow<kotlin.String>");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
openapi: "3.0.1"
info:
title: test
version: "1.0"

paths:

/api/v1/test:
get:
tags: [Test v1]
operationId: test1
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
type: string
Loading