Skip to content

Commit d02c0f4

Browse files
authored
[Kotlin] [Retrofit2] [Coroutines] [Client] Option to remove Response<> wrapper in operation return types (#20613)
* Add useResponseAsReturnType option to kotlin, jvm-retrofit2 and coroutines api template (#15491) * Add useResponseAsReturnType flag to kotlin-jvm-retrofit2-coroutines.yaml sample * Generate sample
1 parent dfbe985 commit d02c0f4

Some content is hidden

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

54 files changed

+3582
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
generatorName: kotlin
2-
outputDir: samples/openapi3/client/petstore/kotlin-jvm-retrofit2-coroutines
2+
outputDir: samples/client/petstore/kotlin-jvm-retrofit2-coroutines
33
library: jvm-retrofit2
4-
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
55
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
66
additionalProperties:
77
serializationLibrary: gson
88
useCoroutines: "true"
99
artifactId: kotlin-petstore-coroutines-client
1010
serializableModel: "true"
1111
dateLibrary: java8
12+
useResponseAsReturnType: false

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
@@ -69,6 +69,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
6969
public static final String USE_SETTINGS_GRADLE = "useSettingsGradle";
7070
public static final String IDEA = "idea";
7171
public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
72+
public static final String USE_RESPONSE_AS_RETURN_TYPE = "useResponseAsReturnType";
7273

7374
public static final String DATE_LIBRARY = "dateLibrary";
7475
public static final String REQUEST_DATE_CONVERTER = "requestDateConverter";
@@ -251,7 +252,7 @@ public KotlinClientCodegen() {
251252
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_PLUGIN_VERSIONS, "Whether to declare Gradle plugin versions in build files."));
252253
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_WRAPPER, "Whether to omit Gradle wrapper for creating a sub project."));
253254
cliOptions.add(CliOption.newBoolean(USE_SETTINGS_GRADLE, "Whether the project uses settings.gradle."));
254-
cliOptions.add(CliOption.newBoolean(IDEA, "Add IntellJ Idea plugin and mark Kotlin main and test folders as source folders."));
255+
cliOptions.add(CliOption.newBoolean(IDEA, "Add IntelliJ Idea plugin and mark Kotlin main and test folders as source folders."));
255256

256257
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."));
257258
cliOptions.add(CliOption.newBoolean(FAIL_ON_UNKNOWN_PROPERTIES, "Fail Jackson de-serialization on unknown properties", false));
@@ -272,6 +273,7 @@ public KotlinClientCodegen() {
272273
cliOptions.add(serializationLibraryOpt.defaultValue(serializationLibrary.name()));
273274

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

277279
@Override
@@ -615,12 +617,22 @@ private void processKotlinxDate() {
615617
private void processJVMRetrofit2Library(String infrastructureFolder) {
616618
additionalProperties.put(JVM, true);
617619
additionalProperties.put(JVM_RETROFIT2, true);
620+
setUseResponseAsReturnType();
618621
supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt"));
619622
supportingFiles.add(new SupportingFile("infrastructure/ResponseExt.kt.mustache", infrastructureFolder, "ResponseExt.kt"));
620623
supportingFiles.add(new SupportingFile("infrastructure/CollectionFormats.kt.mustache", infrastructureFolder, "CollectionFormats.kt"));
621624
addSupportingSerializerAdapters(infrastructureFolder);
622625
}
623626

627+
private void setUseResponseAsReturnType() {
628+
if (additionalProperties.containsKey(USE_RESPONSE_AS_RETURN_TYPE)) {
629+
convertPropertyToBooleanAndWriteBack(USE_RESPONSE_AS_RETURN_TYPE);
630+
} else {
631+
// default is true for backward compatibility
632+
additionalProperties.put(USE_RESPONSE_AS_RETURN_TYPE, true);
633+
}
634+
}
635+
624636
private void processJVMVolleyLibrary(String infrastructureFolder, String requestFolder, String authFolder) {
625637

626638
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}}{{#returnType}}: {{/returnType}}{{^returnType}}{{#useResponseAsReturnType}}: {{/useResponseAsReturnType}}{{/returnType}}{{^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}}{{#useResponseAsReturnType}}Unit{{/useResponseAsReturnType}}{{/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: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@
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 org.jetbrains.annotations.NotNull;
67
import org.openapitools.codegen.ClientOptInput;
78
import org.openapitools.codegen.CodegenConstants;
89
import org.openapitools.codegen.DefaultGenerator;
910
import org.openapitools.codegen.languages.KotlinClientCodegen;
1011
import org.openapitools.codegen.languages.features.CXFServerFeatures;
12+
import org.testng.Assert;
1113
import org.testng.annotations.DataProvider;
1214
import org.testng.annotations.Test;
1315

1416
import java.io.File;
1517
import java.io.IOException;
1618
import java.nio.file.Files;
1719
import java.nio.file.Paths;
20+
import java.util.List;
21+
import java.util.stream.Collectors;
1822

1923
import static org.openapitools.codegen.TestUtils.assertFileContains;
2024

@@ -37,29 +41,81 @@ public Object[][] pathResponses() {
3741
@Test(dataProvider = "clientLibraries")
3842
void testPathVariableIsNotEscaped_19930(ClientLibrary library) throws IOException {
3943

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

4346
KotlinClientCodegen codegen = createCodegen(library);
4447

4548
String outputPath = codegen.getOutputDir().replace('\\', '/');
46-
ClientOptInput input = new ClientOptInput();
47-
input.openAPI(openAPI);
48-
input.config(codegen);
49+
ClientOptInput input = createClientOptInput(openAPI, codegen);
4950

5051
DefaultGenerator generator = new DefaultGenerator();
5152

53+
enableOnlyApiGeneration(generator);
54+
55+
generator.opts(input).generate();
56+
57+
System.out.println(outputPath);
58+
59+
assertFileContains(Paths.get(outputPath + "/src/" + library.getSourceRoot() + "/org/openapitools/client/apis/ArticleApi.kt"), "article('{Id}')");
60+
}
61+
62+
@DataProvider(name = "useResponseAsReturnType")
63+
public static Object[][] useResponseAsReturnTypeTestData() {
64+
return new Object[][]{
65+
{null, "Response<Pet>", ": Response<Unit>"},
66+
{true, "Response<Pet>", ": Response<Unit>"},
67+
{false, "Pet", ""},
68+
{"false", "Pet", ""}};
69+
}
70+
71+
@Test(dataProvider = "useResponseAsReturnType")
72+
public void testUseResponseAsReturnType(Object useResponseAsReturnType, String expectedResponse, String expectedUnitResponse) throws IOException {
73+
OpenAPI openAPI = readOpenAPI("3_0/kotlin/petstore.yaml");
74+
75+
KotlinClientCodegen codegen = createCodegen(ClientLibrary.JVM_RETROFIT2);
76+
codegen.additionalProperties().put(KotlinClientCodegen.USE_COROUTINES, "true");
77+
if (useResponseAsReturnType != null) {
78+
codegen.additionalProperties().put(KotlinClientCodegen.USE_RESPONSE_AS_RETURN_TYPE, useResponseAsReturnType);
79+
}
80+
81+
ClientOptInput input = createClientOptInput(openAPI, codegen);
82+
83+
DefaultGenerator generator = new DefaultGenerator();
84+
85+
enableOnlyApiGeneration(generator);
86+
87+
List<File> files = generator.opts(input).generate();
88+
File petApi = files.stream().filter(file -> file.getName().equals("PetApi.kt")).findAny().orElseThrow();
89+
List<String> lines = Files.readAllLines(petApi.toPath()).stream().map(String::trim).collect(Collectors.toList());
90+
assertFileContainsLine(lines, "suspend fun addPet(@Body pet: Pet): " + expectedResponse);
91+
assertFileContainsLine(lines, "suspend fun deletePet(@Path(\"petId\") petId: kotlin.Long, @Header(\"api_key\") apiKey: kotlin.String? = null)" + expectedUnitResponse);
92+
}
93+
94+
private static void assertFileContainsLine(List<String> lines, String line) {
95+
Assert.assertListContains(lines, s -> s.equals(line), line);
96+
}
97+
98+
private static void enableOnlyApiGeneration(DefaultGenerator generator) {
5299
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "false");
53100
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
54101
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
55102
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
56103
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
104+
generator.setGeneratorPropertyDefault(CodegenConstants.API_TESTS, "false");
105+
generator.setGeneratorPropertyDefault(CodegenConstants.API_DOCS, "false");
106+
}
57107

58-
generator.opts(input).generate();
59-
60-
System.out.println(outputPath);
108+
@NotNull
109+
private static ClientOptInput createClientOptInput(OpenAPI openAPI, KotlinClientCodegen codegen) {
110+
ClientOptInput input = new ClientOptInput();
111+
input.openAPI(openAPI);
112+
input.config(codegen);
113+
return input;
114+
}
61115

62-
assertFileContains(Paths.get(outputPath + "/src/" + library.getSourceRoot() + "/org/openapitools/client/apis/ArticleApi.kt"), "article('{Id}')");
116+
private static OpenAPI readOpenAPI(String url) {
117+
return new OpenAPIParser()
118+
.readLocation(url, null, new ParseOptions()).getOpenAPI();
63119
}
64120

65121
private KotlinClientCodegen createCodegen(ClientLibrary library) throws IOException {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
README.md
2+
build.gradle
3+
docs/ApiResponse.md
4+
docs/Category.md
5+
docs/Order.md
6+
docs/Pet.md
7+
docs/PetApi.md
8+
docs/StoreApi.md
9+
docs/Tag.md
10+
docs/User.md
11+
docs/UserApi.md
12+
gradle/wrapper/gradle-wrapper.jar
13+
gradle/wrapper/gradle-wrapper.properties
14+
gradlew
15+
gradlew.bat
16+
settings.gradle
17+
src/main/kotlin/org/openapitools/client/apis/PetApi.kt
18+
src/main/kotlin/org/openapitools/client/apis/StoreApi.kt
19+
src/main/kotlin/org/openapitools/client/apis/UserApi.kt
20+
src/main/kotlin/org/openapitools/client/auth/ApiKeyAuth.kt
21+
src/main/kotlin/org/openapitools/client/auth/OAuth.kt
22+
src/main/kotlin/org/openapitools/client/auth/OAuthFlow.kt
23+
src/main/kotlin/org/openapitools/client/auth/OAuthOkHttpClient.kt
24+
src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
25+
src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt
26+
src/main/kotlin/org/openapitools/client/infrastructure/CollectionFormats.kt
27+
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt
28+
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt
29+
src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt
30+
src/main/kotlin/org/openapitools/client/infrastructure/ResponseExt.kt
31+
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
32+
src/main/kotlin/org/openapitools/client/models/Category.kt
33+
src/main/kotlin/org/openapitools/client/models/ModelApiResponse.kt
34+
src/main/kotlin/org/openapitools/client/models/Order.kt
35+
src/main/kotlin/org/openapitools/client/models/Pet.kt
36+
src/main/kotlin/org/openapitools/client/models/Tag.kt
37+
src/main/kotlin/org/openapitools/client/models/User.kt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.13.0-SNAPSHOT

0 commit comments

Comments
 (0)