Skip to content

Commit 358e8af

Browse files
authored
[kotlin-spring] Adds useFlowForArrayReturnType option for reactive mode (#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)
1 parent 522b134 commit 358e8af

File tree

76 files changed

+6152
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+6152
-2
lines changed

.github/workflows/samples-kotlin-server.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
- samples/server/petstore/kotlin-springboot-delegate
3535
- samples/server/petstore/kotlin-springboot-modelMutable
3636
- samples/server/petstore/kotlin-springboot-reactive
37+
- samples/server/petstore/kotlin-springboot-reactive-with-flow
38+
- samples/server/petstore/kotlin-springboot-reactive-without-flow
3739
- samples/server/petstore/kotlin-springboot-source-swagger1
3840
- samples/server/petstore/kotlin-springboot-source-swagger2
3941
- samples/server/petstore/kotlin-springboot-springfox
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
generatorName: kotlin-spring
2+
outputDir: samples/server/petstore/kotlin-springboot-reactive-with-flow
3+
library: spring-boot
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
6+
additionalProperties:
7+
documentationProvider: springdoc
8+
annotationLibrary: swagger2
9+
useSwaggerUI: "true"
10+
serviceImplementation: "true"
11+
reactive: "true"
12+
beanValidations: "true"
13+
useFlowForArrayReturnType: "true"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
generatorName: kotlin-spring
2+
outputDir: samples/server/petstore/kotlin-springboot-reactive-without-flow
3+
library: spring-boot
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
6+
additionalProperties:
7+
documentationProvider: springdoc
8+
annotationLibrary: swagger2
9+
useSwaggerUI: "true"
10+
serviceImplementation: "true"
11+
reactive: "true"
12+
beanValidations: "true"
13+
useFlowForArrayReturnType: "false"

docs/generators/kotlin-spring.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5252
|title|server title name or client service name| |OpenAPI Kotlin Spring|
5353
|useBeanValidation|Use BeanValidation API annotations to validate data types| |true|
5454
|useFeignClientUrl|Whether to generate Feign client with url parameter.| |true|
55+
|useFlowForArrayReturnType|Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.| |true|
5556
|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|
5657
|useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true|
5758
|useTags|Whether to use tags for creating interface and controller class names| |false|

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
105105
public static final String BEAN_QUALIFIERS = "beanQualifiers";
106106

107107
public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
108+
public static final String USE_FLOW_FOR_ARRAY_RETURN_TYPE = "useFlowForArrayReturnType";
108109
public static final String REQUEST_MAPPING_OPTION = "requestMappingMode";
109110
public static final String USE_REQUEST_MAPPING_ON_CONTROLLER = "useRequestMappingOnController";
110111
public static final String USE_REQUEST_MAPPING_ON_INTERFACE = "useRequestMappingOnInterface";
@@ -145,6 +146,8 @@ public String getDescription() {
145146
@Setter private boolean serviceImplementation = false;
146147
@Getter @Setter
147148
private boolean reactive = false;
149+
@Getter @Setter
150+
private boolean useFlowForArrayReturnType = true;
148151
@Setter private boolean interfaceOnly = false;
149152
@Setter protected boolean useFeignClientUrl = true;
150153
@Setter protected boolean useFeignClient = false;
@@ -235,6 +238,7 @@ public KotlinSpringServerCodegen() {
235238
"@RestController annotations. May be used to prevent bean names clash if multiple generated libraries" +
236239
" (contexts) added to single project.", beanQualifiers);
237240
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);
241+
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);
238242
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
239243
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
240244
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
@@ -527,10 +531,15 @@ public void processOpts() {
527531
this.setReactive(convertPropertyToBoolean(REACTIVE));
528532
// spring webflux doesn't support @ControllerAdvice
529533
this.setExceptionHandler(false);
534+
535+
if (additionalProperties.containsKey(USE_FLOW_FOR_ARRAY_RETURN_TYPE)) {
536+
this.setUseFlowForArrayReturnType(convertPropertyToBoolean(USE_FLOW_FOR_ARRAY_RETURN_TYPE));
537+
}
530538
}
531539
}
532540
writePropertyBack(REACTIVE, reactive);
533541
writePropertyBack(EXCEPTION_HANDLER, exceptionHandler);
542+
writePropertyBack(USE_FLOW_FOR_ARRAY_RETURN_TYPE, useFlowForArrayReturnType);
534543

535544
if (additionalProperties.containsKey(BEAN_QUALIFIERS) && library.equals(SPRING_BOOT)) {
536545
this.setBeanQualifiers(convertPropertyToBoolean(BEAN_QUALIFIERS));
@@ -685,7 +694,7 @@ public void processOpts() {
685694
supportingFiles.add(new SupportingFile("buildGradleKts.mustache", "build.gradle.kts"));
686695
}
687696
supportingFiles.add(new SupportingFile("settingsGradle.mustache", "settings.gradle"));
688-
697+
689698
String gradleWrapperPackage = "gradle.wrapper";
690699
supportingFiles.add(new SupportingFile("gradlew.mustache", "", "gradlew"));
691700
supportingFiles.add(new SupportingFile("gradlew.bat.mustache", "", "gradlew.bat"));
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isMap}}Map<String, {{{returnType}}}>{{/isMap}}{{#isArray}}{{#reactive}}Flow{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{{returnType}}}{{/returnContainer}}
1+
{{#isMap}}Map<String, {{{returnType}}}>{{/isMap}}{{#isArray}}{{#reactive}}{{#useFlowForArrayReturnType}}Flow{{/useFlowForArrayReturnType}}{{^useFlowForArrayReturnType}}{{{returnContainer}}}{{/useFlowForArrayReturnType}}{{/reactive}}{{^reactive}}{{{returnContainer}}}{{/reactive}}<{{{returnType}}}>{{/isArray}}{{^returnContainer}}{{{returnType}}}{{/returnContainer}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,4 +911,230 @@ public void generateSerializableModel() throws Exception {
911911
"private const val serialVersionUID: kotlin.Long = 1"
912912
);
913913
}
914+
915+
@Test
916+
public void reactiveWithoutFlow() throws Exception {
917+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
918+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
919+
codegen.setOutputDir(output.getAbsolutePath());
920+
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, true);
921+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, false);
922+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
923+
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
924+
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);
925+
926+
List<File> files = new DefaultGenerator()
927+
.opts(
928+
new ClientOptInput()
929+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
930+
.config(codegen)
931+
)
932+
.generate();
933+
934+
Assertions.assertThat(files).contains(
935+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
936+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
937+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
938+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
939+
);
940+
941+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
942+
"Flow<kotlin.String>");
943+
944+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
945+
"List<kotlin.String>");
946+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
947+
"Flow<kotlin.String>");
948+
949+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
950+
"List<kotlin.String>");
951+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
952+
"Flow<kotlin.String>");
953+
954+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
955+
"List<kotlin.String>");
956+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
957+
"Flow<kotlin.String>");
958+
}
959+
960+
@Test
961+
public void reactiveWithFlow() throws Exception {
962+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
963+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
964+
codegen.setOutputDir(output.getAbsolutePath());
965+
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, true);
966+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, true);
967+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
968+
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
969+
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);
970+
971+
List<File> files = new DefaultGenerator()
972+
.opts(
973+
new ClientOptInput()
974+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
975+
.config(codegen)
976+
)
977+
.generate();
978+
979+
Assertions.assertThat(files).contains(
980+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
981+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
982+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
983+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
984+
);
985+
986+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
987+
"List<kotlin.String>");
988+
989+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
990+
"List<kotlin.String>");
991+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
992+
"Flow<kotlin.String>");
993+
994+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
995+
"List<kotlin.String>");
996+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
997+
"Flow<kotlin.String>");
998+
999+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1000+
"List<kotlin.String>");
1001+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1002+
"Flow<kotlin.String>");
1003+
}
1004+
1005+
@Test
1006+
public void reactiveWithDefaultValueFlow() throws Exception {
1007+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1008+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
1009+
codegen.setOutputDir(output.getAbsolutePath());
1010+
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, true);
1011+
// should use default 'true' instead
1012+
// codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, true);
1013+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
1014+
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
1015+
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);
1016+
1017+
List<File> files = new DefaultGenerator()
1018+
.opts(
1019+
new ClientOptInput()
1020+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
1021+
.config(codegen)
1022+
)
1023+
.generate();
1024+
1025+
Assertions.assertThat(files).contains(
1026+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1027+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
1028+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1029+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
1030+
);
1031+
1032+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
1033+
"List<kotlin.String>");
1034+
1035+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1036+
"List<kotlin.String>");
1037+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1038+
"Flow<kotlin.String>");
1039+
1040+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1041+
"List<kotlin.String>");
1042+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1043+
"Flow<kotlin.String>");
1044+
1045+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1046+
"List<kotlin.String>");
1047+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1048+
"Flow<kotlin.String>");
1049+
}
1050+
1051+
@Test
1052+
public void nonReactiveWithoutFlow() throws Exception {
1053+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1054+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
1055+
codegen.setOutputDir(output.getAbsolutePath());
1056+
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, false);
1057+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, false);
1058+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
1059+
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
1060+
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);
1061+
1062+
List<File> files = new DefaultGenerator()
1063+
.opts(
1064+
new ClientOptInput()
1065+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
1066+
.config(codegen)
1067+
)
1068+
.generate();
1069+
1070+
Assertions.assertThat(files).contains(
1071+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1072+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
1073+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1074+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
1075+
);
1076+
1077+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
1078+
"Flow<kotlin.String>");
1079+
1080+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1081+
"List<kotlin.String>");
1082+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1083+
"Flow<kotlin.String>");
1084+
1085+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1086+
"List<kotlin.String>");
1087+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1088+
"Flow<kotlin.String>");
1089+
1090+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1091+
"List<kotlin.String>");
1092+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1093+
"Flow<kotlin.String>");
1094+
}
1095+
1096+
@Test
1097+
public void nonReactiveWithFlow() throws Exception {
1098+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1099+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
1100+
codegen.setOutputDir(output.getAbsolutePath());
1101+
codegen.additionalProperties().put(KotlinSpringServerCodegen.REACTIVE, false);
1102+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_FLOW_FOR_ARRAY_RETURN_TYPE, true);
1103+
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);
1104+
codegen.additionalProperties().put(KotlinSpringServerCodegen.SERVICE_IMPLEMENTATION, true);
1105+
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);
1106+
1107+
List<File> files = new DefaultGenerator()
1108+
.opts(
1109+
new ClientOptInput()
1110+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue16130-add-useFlowForArrayReturnType-param.yaml"))
1111+
.config(codegen)
1112+
)
1113+
.generate();
1114+
1115+
Assertions.assertThat(files).contains(
1116+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1117+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
1118+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1119+
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiService.kt")
1120+
);
1121+
1122+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
1123+
"Flow<kotlin.String>");
1124+
1125+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1126+
"List<kotlin.String>");
1127+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1Api.kt"),
1128+
"Flow<kotlin.String>");
1129+
1130+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1131+
"List<kotlin.String>");
1132+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
1133+
"Flow<kotlin.String>");
1134+
1135+
assertFileContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1136+
"List<kotlin.String>");
1137+
assertFileNotContains(Paths.get(output + "/src/main/kotlin/org/openapitools/api/TestV1ApiService.kt"),
1138+
"Flow<kotlin.String>");
1139+
}
9141140
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
openapi: "3.0.1"
2+
info:
3+
title: test
4+
version: "1.0"
5+
6+
paths:
7+
8+
/api/v1/test:
9+
get:
10+
tags: [Test v1]
11+
operationId: test1
12+
responses:
13+
'200':
14+
description: OK
15+
content:
16+
application/json:
17+
schema:
18+
type: array
19+
items:
20+
type: string

0 commit comments

Comments
 (0)