Skip to content

Commit

Permalink
[kotlin-spring] Adds useFlowForArrayReturnType option for reactive mo…
Browse files Browse the repository at this point in the history
…de (#16130) (#20409)

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

* [kotlin-spring] Replaces manual doc change with generated one (#16130)

* [kotlin-spring] Fixes errors (#16130)

* [kotlin-spring] Adds samples (#16130)
  • Loading branch information
hu553in authored Jan 11, 2025
1 parent 522b134 commit 358e8af
Show file tree
Hide file tree
Showing 76 changed files with 6,152 additions and 2 deletions.
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

0 comments on commit 358e8af

Please sign in to comment.