Skip to content

Commit d8bdbf9

Browse files
committed
Add useResponseAsReturnType option to kotlin, jvm-retrofit2 and coroutines api template (#15491)
1 parent 79f70dc commit d8bdbf9

File tree

4 files changed

+69
-14
lines changed

4 files changed

+69
-14
lines changed

docs/generators/kotlin.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
3030
|generateOneOfAnyOfWrappers|Generate oneOf, anyOf schemas as wrappers. Only `jvm-retrofit2`(library), `gson`(serializationLibrary) support this option.| |false|
3131
|generateRoomModels|Generate Android Room database models in addition to API models (JVM Volley library only)| |false|
3232
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
33-
|idea|Add IntellJ Idea plugin and mark Kotlin main and test folders as source folders.| |false|
33+
|idea|Add IntelliJ Idea plugin and mark Kotlin main and test folders as source folders.| |false|
3434
|library|Library template (sub-template) to use|<dl><dt>**jvm-ktor**</dt><dd>Platform: Java Virtual Machine. HTTP client: Ktor 1.6.7. JSON processing: Gson, Jackson (default).</dd><dt>**jvm-okhttp4**</dt><dd>[DEFAULT] Platform: Java Virtual Machine. HTTP client: OkHttp 4.2.0 (Android 5.0+ and Java 8+). JSON processing: Moshi 1.8.0.</dd><dt>**jvm-spring-webclient**</dt><dd>Platform: Java Virtual Machine. HTTP: Spring 5 (or 6 with useSpringBoot3 enabled) WebClient. JSON processing: Jackson.</dd><dt>**jvm-spring-restclient**</dt><dd>Platform: Java Virtual Machine. HTTP: Spring 6 RestClient. JSON processing: Jackson.</dd><dt>**jvm-retrofit2**</dt><dd>Platform: Java Virtual Machine. HTTP client: Retrofit 2.6.2.</dd><dt>**multiplatform**</dt><dd>Platform: Kotlin multiplatform. HTTP client: Ktor 1.6.7. JSON processing: Kotlinx Serialization: 1.2.1.</dd><dt>**jvm-volley**</dt><dd>Platform: JVM for Android. HTTP client: Volley 1.2.1. JSON processing: gson 2.8.9</dd><dt>**jvm-vertx**</dt><dd>Platform: Java Virtual Machine. HTTP client: Vert.x Web Client. JSON processing: Moshi, Gson or Jackson.</dd></dl>|jvm-okhttp4|
3535
|mapFileBinaryToByteArray|Map File and Binary to ByteArray (default: false)| |false|
3636
|modelMutable|Create mutable models| |false|
@@ -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`&lt;`T`&gt; 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: 13 additions & 1 deletion
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";
@@ -265,7 +266,7 @@ public KotlinClientCodegen() {
265266
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_PLUGIN_VERSIONS, "Whether to declare Gradle plugin versions in build files."));
266267
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_WRAPPER, "Whether to omit Gradle wrapper for creating a sub project."));
267268
cliOptions.add(CliOption.newBoolean(USE_SETTINGS_GRADLE, "Whether the project uses settings.gradle."));
268-
cliOptions.add(CliOption.newBoolean(IDEA, "Add IntellJ Idea plugin and mark Kotlin main and test folders as source folders."));
269+
cliOptions.add(CliOption.newBoolean(IDEA, "Add IntelliJ Idea plugin and mark Kotlin main and test folders as source folders."));
269270

270271
cliOptions.add(CliOption.newBoolean(MOSHI_CODE_GEN, "Whether to enable codegen with the Moshi library. Refer to the [official Moshi doc](https://github.com/square/moshi#codegen) for more info."));
271272
cliOptions.add(CliOption.newBoolean(FAIL_ON_UNKNOWN_PROPERTIES, "Fail Jackson de-serialization on unknown properties", false));
@@ -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`.", true));
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
@@ -155,7 +155,7 @@ import okhttp3.MultipartBody
155155
{{/prioritizedContentTypes}}
156156
{{/formParams}}
157157
@{{httpMethod}}("{{{path}}}")
158-
{{^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}}
158+
{{^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}}
159159

160160
{{/operation}}
161161
}

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 = "useResponseAsReturnType")
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)