Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Kotlin] [Retrofit2] [Coroutines] [Client] Option to remove Response<> wrapper in operation return types #20613

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
generatorName: kotlin
outputDir: samples/openapi3/client/petstore/kotlin-jvm-retrofit2-coroutines
outputDir: samples/client/petstore/kotlin-jvm-retrofit2-coroutines
library: jvm-retrofit2
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
serializationLibrary: gson
useCoroutines: "true"
artifactId: kotlin-petstore-coroutines-client
serializableModel: "true"
dateLibrary: java8
useResponseAsReturnType: false
3 changes: 2 additions & 1 deletion docs/generators/kotlin.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|generateOneOfAnyOfWrappers|Generate oneOf, anyOf schemas as wrappers. Only `jvm-retrofit2`(library), `gson`(serializationLibrary) support this option.| |false|
|generateRoomModels|Generate Android Room database models in addition to API models (JVM Volley library only)| |false|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|idea|Add IntellJ Idea plugin and mark Kotlin main and test folders as source folders.| |false|
|idea|Add IntelliJ Idea plugin and mark Kotlin main and test folders as source folders.| |false|
|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|
|mapFileBinaryToByteArray|Map File and Binary to ByteArray (default: false)| |false|
|modelMutable|Create mutable models| |false|
Expand All @@ -50,6 +50,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|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|
|useCoroutines|Whether to use the Coroutines adapter with the retrofit2 library.| |false|
|useNonAsciiHeaders|Allow to use non-ascii headers with the okhttp library| |false|
|useResponseAsReturnType|When using retrofit2 and coroutines, use `Response`&lt;`T`&gt; as return type instead of `T`.| |true|
|useRxJava3|Whether to use the RxJava3 adapter with the retrofit2 library.| |false|
|useSettingsGradle|Whether the project uses settings.gradle.| |false|
|useSpringBoot3|Whether to use the Spring Boot 3 with the jvm-spring-webclient library.| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
public static final String USE_SETTINGS_GRADLE = "useSettingsGradle";
public static final String IDEA = "idea";
public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
public static final String USE_RESPONSE_AS_RETURN_TYPE = "useResponseAsReturnType";

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

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."));
cliOptions.add(CliOption.newBoolean(FAIL_ON_UNKNOWN_PROPERTIES, "Fail Jackson de-serialization on unknown properties", false));
Expand All @@ -286,6 +287,7 @@ public KotlinClientCodegen() {
cliOptions.add(serializationLibraryOpt.defaultValue(serializationLibrary.name()));

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

@Override
Expand Down Expand Up @@ -629,12 +631,22 @@ private void processKotlinxDate() {
private void processJVMRetrofit2Library(String infrastructureFolder) {
additionalProperties.put(JVM, true);
additionalProperties.put(JVM_RETROFIT2, true);
setUseResponseAsReturnType();
supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt"));
supportingFiles.add(new SupportingFile("infrastructure/ResponseExt.kt.mustache", infrastructureFolder, "ResponseExt.kt"));
supportingFiles.add(new SupportingFile("infrastructure/CollectionFormats.kt.mustache", infrastructureFolder, "CollectionFormats.kt"));
addSupportingSerializerAdapters(infrastructureFolder);
}

private void setUseResponseAsReturnType() {
if (additionalProperties.containsKey(USE_RESPONSE_AS_RETURN_TYPE)) {
convertPropertyToBooleanAndWriteBack(USE_RESPONSE_AS_RETURN_TYPE);
} else {
// default is true for backward compatibility
additionalProperties.put(USE_RESPONSE_AS_RETURN_TYPE, true);
}
}

private void processJVMVolleyLibrary(String infrastructureFolder, String requestFolder, String authFolder) {

additionalProperties.put(JVM, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ import okhttp3.MultipartBody
{{/prioritizedContentTypes}}
{{/formParams}}
@{{httpMethod}}("{{{path}}}")
{{^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}}
{{^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}}

{{/operation}}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.parser.core.models.ParseOptions;
import lombok.Getter;
import org.jetbrains.kotlin.com.intellij.openapi.util.text.Strings;
import org.jetbrains.annotations.NotNull;
import org.openapitools.codegen.ClientOptInput;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.languages.KotlinClientCodegen;
import org.openapitools.codegen.languages.features.CXFServerFeatures;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

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

Expand All @@ -39,29 +41,81 @@ public Object[][] pathResponses() {
@Test(dataProvider = "clientLibraries")
void testPathVariableIsNotEscaped_19930(ClientLibrary library) throws IOException {

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

KotlinClientCodegen codegen = createCodegen(library);

String outputPath = codegen.getOutputDir().replace('\\', '/');
ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);
ClientOptInput input = createClientOptInput(openAPI, codegen);

DefaultGenerator generator = new DefaultGenerator();

enableOnlyApiGeneration(generator);

generator.opts(input).generate();

System.out.println(outputPath);

assertFileContains(Paths.get(outputPath + "/src/" + library.getSourceRoot() + "/org/openapitools/client/apis/ArticleApi.kt"), "article('{Id}')");
}

@DataProvider(name = "useResponseAsReturnType")
public static Object[][] useResponseAsReturnTypeTestData() {
return new Object[][]{
{null, "Response<Pet>", ": Response<Unit>"},
{true, "Response<Pet>", ": Response<Unit>"},
{false, "Pet", ""},
{"false", "Pet", ""}};
}

@Test(dataProvider = "useResponseAsReturnType")
public void testUseResponseAsReturnType(Object useResponseAsReturnType, String expectedResponse, String expectedUnitResponse) throws IOException {
OpenAPI openAPI = readOpenAPI("3_0/kotlin/petstore.yaml");

KotlinClientCodegen codegen = createCodegen(ClientLibrary.JVM_RETROFIT2);
codegen.additionalProperties().put(KotlinClientCodegen.USE_COROUTINES, "true");
if (useResponseAsReturnType != null) {
codegen.additionalProperties().put(KotlinClientCodegen.USE_RESPONSE_AS_RETURN_TYPE, useResponseAsReturnType);
}

ClientOptInput input = createClientOptInput(openAPI, codegen);

DefaultGenerator generator = new DefaultGenerator();

enableOnlyApiGeneration(generator);

List<File> files = generator.opts(input).generate();
File petApi = files.stream().filter(file -> file.getName().equals("PetApi.kt")).findAny().orElseThrow();
List<String> lines = Files.readAllLines(petApi.toPath()).stream().map(String::trim).collect(Collectors.toList());
assertFileContainsLine(lines, "suspend fun addPet(@Body pet: Pet): " + expectedResponse);
assertFileContainsLine(lines, "suspend fun deletePet(@Path(\"petId\") petId: kotlin.Long, @Header(\"api_key\") apiKey: kotlin.String? = null)" + expectedUnitResponse);
}

private static void assertFileContainsLine(List<String> lines, String line) {
Assert.assertListContains(lines, s -> s.equals(line), line);
}

private static void enableOnlyApiGeneration(DefaultGenerator generator) {
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.API_TESTS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.API_DOCS, "false");
}

generator.opts(input).generate();

System.out.println(outputPath);
@NotNull
private static ClientOptInput createClientOptInput(OpenAPI openAPI, KotlinClientCodegen codegen) {
ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);
return input;
}

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

private KotlinClientCodegen createCodegen(ClientLibrary library) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.openapi-generator-ignore
README.md
build.gradle
docs/ApiResponse.md
docs/Category.md
docs/Order.md
docs/Pet.md
docs/PetApi.md
docs/StoreApi.md
docs/Tag.md
docs/User.md
docs/UserApi.md
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
settings.gradle
src/main/kotlin/org/openapitools/client/apis/PetApi.kt
src/main/kotlin/org/openapitools/client/apis/StoreApi.kt
src/main/kotlin/org/openapitools/client/apis/UserApi.kt
src/main/kotlin/org/openapitools/client/auth/ApiKeyAuth.kt
src/main/kotlin/org/openapitools/client/auth/OAuth.kt
src/main/kotlin/org/openapitools/client/auth/OAuthFlow.kt
src/main/kotlin/org/openapitools/client/auth/OAuthOkHttpClient.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/CollectionFormats.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/ResponseExt.kt
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
src/main/kotlin/org/openapitools/client/models/Category.kt
src/main/kotlin/org/openapitools/client/models/ModelApiResponse.kt
src/main/kotlin/org/openapitools/client/models/Order.kt
src/main/kotlin/org/openapitools/client/models/Pet.kt
src/main/kotlin/org/openapitools/client/models/Tag.kt
src/main/kotlin/org/openapitools/client/models/User.kt
src/test/kotlin/org/openapitools/client/apis/PetApiTest.kt
src/test/kotlin/org/openapitools/client/apis/StoreApiTest.kt
src/test/kotlin/org/openapitools/client/apis/UserApiTest.kt
src/test/kotlin/org/openapitools/client/models/ApiResponseTest.kt
src/test/kotlin/org/openapitools/client/models/CategoryTest.kt
src/test/kotlin/org/openapitools/client/models/OrderTest.kt
src/test/kotlin/org/openapitools/client/models/PetTest.kt
src/test/kotlin/org/openapitools/client/models/TagTest.kt
src/test/kotlin/org/openapitools/client/models/UserTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.12.0-SNAPSHOT
Loading