Skip to content

Commit 0563ce7

Browse files
committed
Add useResponseAsReturnType option to kotlin, jvm-retrofit2 and coroutines api template (#15491)
1 parent c74dfa3 commit 0563ce7

File tree

4 files changed

+67
-12
lines changed

4 files changed

+67
-12
lines changed

docs/generators/kotlin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5050
|supportAndroidApiLevel25AndBelow|[WARNING] This flag will generate code that has a known security vulnerability. It uses `kotlin.io.createTempFile` instead of `java.nio.file.Files.createTempFile` in order to support Android API level 25 and below. For more info, please check the following links https://github.com/OpenAPITools/openapi-generator/security/advisories/GHSA-23x4-m842-fmwf, https://github.com/OpenAPITools/openapi-generator/pull/9284| |false|
5151
|useCoroutines|Whether to use the Coroutines adapter with the retrofit2 library.| |false|
5252
|useNonAsciiHeaders|Allow to use non-ascii headers with the okhttp library| |false|
53+
|useResponseAsReturnType|When using retrofit2 and coroutines, use `Response<T>` as return type instead of `T`.| |true|
5354
|useRxJava3|Whether to use the RxJava3 adapter with the retrofit2 library.| |false|
5455
|useSettingsGradle|Whether the project uses settings.gradle.| |false|
5556
|useSpringBoot3|Whether to use the Spring Boot 3 with the jvm-spring-webclient library.| |false|

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
8383
public static final String USE_SETTINGS_GRADLE = "useSettingsGradle";
8484
public static final String IDEA = "idea";
8585
public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
86+
public static final String USE_RESPONSE_AS_RETURN_TYPE = "useResponseAsReturnType";
8687

8788
public static final String DATE_LIBRARY = "dateLibrary";
8889
public static final String REQUEST_DATE_CONVERTER = "requestDateConverter";
@@ -286,6 +287,7 @@ public KotlinClientCodegen() {
286287
cliOptions.add(serializationLibraryOpt.defaultValue(serializationLibrary.name()));
287288

288289
cliOptions.add(CliOption.newBoolean(USE_NON_ASCII_HEADERS, "Allow to use non-ascii headers with the okhttp library"));
290+
cliOptions.add(CliOption.newBoolean(USE_RESPONSE_AS_RETURN_TYPE, "When using retrofit2 and coroutines, use `Response<T>` as return type instead of `T`."));
289291
}
290292

291293
@Override
@@ -629,12 +631,22 @@ private void processKotlinxDate() {
629631
private void processJVMRetrofit2Library(String infrastructureFolder) {
630632
additionalProperties.put(JVM, true);
631633
additionalProperties.put(JVM_RETROFIT2, true);
634+
setUseResponseAsReturnType();
632635
supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt"));
633636
supportingFiles.add(new SupportingFile("infrastructure/ResponseExt.kt.mustache", infrastructureFolder, "ResponseExt.kt"));
634637
supportingFiles.add(new SupportingFile("infrastructure/CollectionFormats.kt.mustache", infrastructureFolder, "CollectionFormats.kt"));
635638
addSupportingSerializerAdapters(infrastructureFolder);
636639
}
637640

641+
private void setUseResponseAsReturnType() {
642+
if (additionalProperties.containsKey(USE_RESPONSE_AS_RETURN_TYPE)) {
643+
convertPropertyToBooleanAndWriteBack(USE_RESPONSE_AS_RETURN_TYPE);
644+
} else {
645+
// default is true for backward compatibility
646+
additionalProperties.put(USE_RESPONSE_AS_RETURN_TYPE, true);
647+
}
648+
}
649+
638650
private void processJVMVolleyLibrary(String infrastructureFolder, String requestFolder, String authFolder) {
639651

640652
additionalProperties.put(JVM, true);

modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-retrofit2/api.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ import okhttp3.MultipartBody
154154
{{/prioritizedContentTypes}}
155155
{{/formParams}}
156156
@{{httpMethod}}("{{{path}}}")
157-
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}{{^doNotUseRxAndCoroutines}}{{#useCoroutines}}suspend {{/useCoroutines}}{{/doNotUseRxAndCoroutines}}fun {{operationId}}({{^allParams}}){{/allParams}}{{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/allParams}}: {{^doNotUseRxAndCoroutines}}{{#useRxJava}}Observable<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}>{{/useRxJava}}{{#useRxJava2}}{{#returnType}}Single<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}Completable{{/returnType}}{{/useRxJava2}}{{#useRxJava3}}{{#returnType}}Single<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}Completable{{/returnType}}{{/useRxJava3}}{{#useCoroutines}}Response<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}>{{/useCoroutines}}{{/doNotUseRxAndCoroutines}}{{#doNotUseRxAndCoroutines}}Call<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}>{{/doNotUseRxAndCoroutines}}
157+
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}{{^doNotUseRxAndCoroutines}}{{#useCoroutines}}suspend {{/useCoroutines}}{{/doNotUseRxAndCoroutines}}fun {{operationId}}({{^allParams}}){{/allParams}}{{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/allParams}}: {{^doNotUseRxAndCoroutines}}{{#useRxJava}}Observable<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}>{{/useRxJava}}{{#useRxJava2}}{{#returnType}}Single<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}Completable{{/returnType}}{{/useRxJava2}}{{#useRxJava3}}{{#returnType}}Single<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{/isResponseFile}}>{{/returnType}}{{^returnType}}Completable{{/returnType}}{{/useRxJava3}}{{#useCoroutines}}{{#useResponseAsReturnType}}Response<{{/useResponseAsReturnType}}{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}{{#useResponseAsReturnType}}>{{/useResponseAsReturnType}}{{/useCoroutines}}{{/doNotUseRxAndCoroutines}}{{#doNotUseRxAndCoroutines}}Call<{{#isResponseFile}}ResponseBody{{/isResponseFile}}{{^isResponseFile}}{{{returnType}}}{{^returnType}}Unit{{/returnType}}{{/isResponseFile}}>{{/doNotUseRxAndCoroutines}}
158158

159159
{{/operation}}
160160
}

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

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
import io.swagger.parser.OpenAPIParser;
44
import io.swagger.v3.oas.models.OpenAPI;
55
import io.swagger.v3.parser.core.models.ParseOptions;
6-
import lombok.Getter;
7-
import org.jetbrains.kotlin.com.intellij.openapi.util.text.Strings;
6+
import org.jetbrains.annotations.NotNull;
87
import org.openapitools.codegen.ClientOptInput;
98
import org.openapitools.codegen.CodegenConstants;
109
import org.openapitools.codegen.DefaultGenerator;
@@ -17,6 +16,7 @@
1716
import java.io.IOException;
1817
import java.nio.file.Files;
1918
import java.nio.file.Paths;
19+
import java.util.List;
2020

2121
import static org.openapitools.codegen.TestUtils.assertFileContains;
2222

@@ -39,29 +39,71 @@ public Object[][] pathResponses() {
3939
@Test(dataProvider = "clientLibraries")
4040
void testPathVariableIsNotEscaped_19930(ClientLibrary library) throws IOException {
4141

42-
OpenAPI openAPI = new OpenAPIParser()
43-
.readLocation("src/test/resources/3_0/kotlin/issue19930-path-escaping.json", null, new ParseOptions()).getOpenAPI();
42+
OpenAPI openAPI = readOpenAPI("src/test/resources/3_0/kotlin/issue19930-path-escaping.json");
4443

4544
KotlinClientCodegen codegen = createCodegen(library);
4645

4746
String outputPath = codegen.getOutputDir().replace('\\', '/');
48-
ClientOptInput input = new ClientOptInput();
49-
input.openAPI(openAPI);
50-
input.config(codegen);
47+
ClientOptInput input = createClientOptInput(openAPI, codegen);
48+
49+
DefaultGenerator generator = new DefaultGenerator();
50+
51+
enableOnlyApiGeneration(generator);
52+
53+
generator.opts(input).generate();
54+
55+
System.out.println(outputPath);
56+
57+
assertFileContains(Paths.get(outputPath + "/src/" + library.getSourceRoot() + "/org/openapitools/client/apis/ArticleApi.kt"), "article('{Id}')");
58+
}
59+
60+
@DataProvider(name = "useResponseAsReturnType")
61+
public static Object[][] useResponseAsReturnTypeTestData() {
62+
return new Object[][]{{null, "Response<Pet>"}, {true, "Response<Pet>"}, {false, "Pet"}, {"false", "Pet"}}; // null is default
63+
}
64+
65+
@Test(dataProvider = "useResponseAsReturnTypeTestData")
66+
public void testUseResponseAsReturnType(Object useResponseAsReturnType, String expectedResponse) throws IOException {
67+
OpenAPI openAPI = readOpenAPI("3_0/kotlin/petstore.yaml");
68+
69+
KotlinClientCodegen codegen = createCodegen(ClientLibrary.JVM_RETROFIT2);
70+
codegen.additionalProperties().put(KotlinClientCodegen.USE_COROUTINES, "true");
71+
if (useResponseAsReturnType != null) {
72+
codegen.additionalProperties().put(KotlinClientCodegen.USE_RESPONSE_AS_RETURN_TYPE, useResponseAsReturnType);
73+
}
74+
75+
ClientOptInput input = createClientOptInput(openAPI, codegen);
5176

5277
DefaultGenerator generator = new DefaultGenerator();
5378

79+
enableOnlyApiGeneration(generator);
80+
81+
List<File> files = generator.opts(input).generate();
82+
File petApi = files.stream().filter(file -> file.getName().equals("PetApi.kt")).findAny().orElseThrow();
83+
assertFileContains(petApi.toPath(), "suspend fun addPet(@Body pet: Pet): " + expectedResponse);
84+
}
85+
86+
private static void enableOnlyApiGeneration(DefaultGenerator generator) {
5487
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "false");
5588
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
5689
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
5790
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
5891
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
92+
generator.setGeneratorPropertyDefault(CodegenConstants.API_TESTS, "false");
93+
generator.setGeneratorPropertyDefault(CodegenConstants.API_DOCS, "false");
94+
}
5995

60-
generator.opts(input).generate();
61-
62-
System.out.println(outputPath);
96+
@NotNull
97+
private static ClientOptInput createClientOptInput(OpenAPI openAPI, KotlinClientCodegen codegen) {
98+
ClientOptInput input = new ClientOptInput();
99+
input.openAPI(openAPI);
100+
input.config(codegen);
101+
return input;
102+
}
63103

64-
assertFileContains(Paths.get(outputPath + "/src/" + library.getSourceRoot() + "/org/openapitools/client/apis/ArticleApi.kt"), "article('{Id}')");
104+
private static OpenAPI readOpenAPI(String url) {
105+
return new OpenAPIParser()
106+
.readLocation(url, null, new ParseOptions()).getOpenAPI();
65107
}
66108

67109
private KotlinClientCodegen createCodegen(ClientLibrary library) throws IOException {

0 commit comments

Comments
 (0)