Skip to content

Commit 6bd7036

Browse files
authored
[kotlin-client] Add ktor library with Jackson and GSON processing support (#11838)
1 parent bee9c79 commit 6bd7036

89 files changed

Lines changed: 6509 additions & 12 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
generatorName: kotlin
2+
outputDir: samples/client/petstore/kotlin-jvm-ktor-gson
3+
library: jvm-ktor
4+
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
6+
additionalProperties:
7+
artifactId: kotlin-petstore-jvm-ktor-gson
8+
enumUnknownDefaultCase: true
9+
serializationLibrary: gson
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
generatorName: kotlin
2+
outputDir: samples/client/petstore/kotlin-jvm-ktor-jackson
3+
library: jvm-ktor
4+
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
6+
additionalProperties:
7+
artifactId: kotlin-petstore-jvm-ktor-jackson
8+
enumUnknownDefaultCase: true
9+
serializationLibrary: jackson

docs/generators/kotlin.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
2626
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |camelCase|
2727
|generateRoomModels|Generate Android Room database models in addition to API models (JVM Volley library only)| |false|
2828
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
29-
|library|Library template (sub-template) to use|<dl><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-okhttp3**</dt><dd>Platform: Java Virtual Machine. HTTP client: OkHttp 3.12.4 (Android 2.3+ and Java 7+). JSON processing: Moshi 1.8.0.</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.0. 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></dl>|jvm-okhttp4|
29+
|library|Library template (sub-template) to use|<dl><dt>**jvm-ktor**</dt><dd>Platform: Java Virtual Machine. HTTP client: Ktor 1.6.7.</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-okhttp3**</dt><dd>Platform: Java Virtual Machine. HTTP client: OkHttp 3.12.4 (Android 2.3+ and Java 7+). JSON processing: Moshi 1.8.0.</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></dl>|jvm-okhttp4|
3030
|modelMutable|Create mutable models| |false|
3131
|moshiCodeGen|Whether to enable codegen with the Moshi library. Refer to the [official Moshi doc](https://github.com/square/moshi#codegen) for more info.| |false|
3232
|omitGradlePluginVersions|Whether to declare Gradle plugin versions in build files.| |false|

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

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717

1818
package org.openapitools.codegen.languages;
1919

20+
import java.io.File;
21+
import java.util.HashMap;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.function.Predicate;
25+
import java.util.stream.Collectors;
26+
import java.util.stream.Stream;
27+
2028
import org.apache.commons.lang3.StringUtils;
2129
import org.openapitools.codegen.CliOption;
2230
import org.openapitools.codegen.CodegenConstants;
@@ -41,21 +49,14 @@
4149
import org.slf4j.Logger;
4250
import org.slf4j.LoggerFactory;
4351

44-
import java.io.File;
45-
import java.util.HashMap;
46-
import java.util.List;
47-
import java.util.Map;
48-
import java.util.function.Predicate;
49-
import java.util.stream.Collectors;
50-
import java.util.stream.Stream;
51-
5252
import static java.util.Collections.sort;
5353

5454
public class KotlinClientCodegen extends AbstractKotlinCodegen {
5555

5656
private final Logger LOGGER = LoggerFactory.getLogger(KotlinClientCodegen.class);
5757

5858
protected static final String JVM = "jvm";
59+
protected static final String JVM_KTOR = "jvm-ktor";
5960
protected static final String JVM_OKHTTP = "jvm-okhttp";
6061
protected static final String JVM_OKHTTP4 = "jvm-okhttp4";
6162
protected static final String JVM_OKHTTP3 = "jvm-okhttp3";
@@ -206,10 +207,11 @@ public KotlinClientCodegen() {
206207
collectionType.setDefault(this.collectionType);
207208
cliOptions.add(collectionType);
208209

210+
supportedLibraries.put(JVM_KTOR, "Platform: Java Virtual Machine. HTTP client: Ktor 1.6.7. JSON processing: Gson, Jackson (default).");
209211
supportedLibraries.put(JVM_OKHTTP4, "[DEFAULT] Platform: Java Virtual Machine. HTTP client: OkHttp 4.2.0 (Android 5.0+ and Java 8+). JSON processing: Moshi 1.8.0.");
210212
supportedLibraries.put(JVM_OKHTTP3, "Platform: Java Virtual Machine. HTTP client: OkHttp 3.12.4 (Android 2.3+ and Java 7+). JSON processing: Moshi 1.8.0.");
211213
supportedLibraries.put(JVM_RETROFIT2, "Platform: Java Virtual Machine. HTTP client: Retrofit 2.6.2.");
212-
supportedLibraries.put(MULTIPLATFORM, "Platform: Kotlin multiplatform. HTTP client: Ktor 1.6.0. JSON processing: Kotlinx Serialization: 1.2.1.");
214+
supportedLibraries.put(MULTIPLATFORM, "Platform: Kotlin multiplatform. HTTP client: Ktor 1.6.7. JSON processing: Kotlinx Serialization: 1.2.1.");
213215
supportedLibraries.put(JVM_VOLLEY, "Platform: JVM for Android. HTTP client: Volley 1.2.1. JSON processing: gson 2.8.9");
214216

215217
CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "Library template (sub-template) to use");
@@ -419,6 +421,9 @@ public void processOpts() {
419421
commonSupportingFiles();
420422

421423
switch (getLibrary()) {
424+
case JVM_KTOR:
425+
processJVMKtorLibrary(infrastructureFolder);
426+
break;
422427
case JVM_OKHTTP3:
423428
case JVM_OKHTTP4:
424429
processJVMOkHttpLibrary(infrastructureFolder);
@@ -630,6 +635,32 @@ private void addSupportingSerializerAdapters(final String infrastructureFolder)
630635
}
631636
}
632637

638+
private void processJVMKtorLibrary(final String infrastructureFolder) {
639+
// in future kotlinx.serialization may be added
640+
if (this.serializationLibrary != SERIALIZATION_LIBRARY_TYPE.gson && this.serializationLibrary != SERIALIZATION_LIBRARY_TYPE.jackson) {
641+
this.serializationLibrary = SERIALIZATION_LIBRARY_TYPE.jackson;
642+
}
643+
644+
additionalProperties.put(JVM, true);
645+
additionalProperties.put(JVM_KTOR, true);
646+
647+
defaultIncludes.add("io.ktor.client.request.forms.InputProvider");
648+
649+
importMapping.put("InputProvider", "io.ktor.client.request.forms.InputProvider");
650+
651+
supportingFiles.add(new SupportingFile("infrastructure/ApiAbstractions.kt.mustache", infrastructureFolder, "ApiAbstractions.kt"));
652+
supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt"));
653+
supportingFiles.add(new SupportingFile("infrastructure/HttpResponse.kt.mustache", infrastructureFolder, "HttpResponse.kt"));
654+
supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt"));
655+
supportingFiles.add(new SupportingFile("infrastructure/RequestMethod.kt.mustache", infrastructureFolder, "RequestMethod.kt"));
656+
657+
supportingFiles.add(new SupportingFile("auth/ApiKeyAuth.kt.mustache", authFolder, "ApiKeyAuth.kt"));
658+
supportingFiles.add(new SupportingFile("auth/Authentication.kt.mustache", authFolder, "Authentication.kt"));
659+
supportingFiles.add(new SupportingFile("auth/HttpBasicAuth.kt.mustache", authFolder, "HttpBasicAuth.kt"));
660+
supportingFiles.add(new SupportingFile("auth/HttpBearerAuth.kt.mustache", authFolder, "HttpBearerAuth.kt"));
661+
supportingFiles.add(new SupportingFile("auth/OAuth.kt.mustache", authFolder, "OAuth.kt"));
662+
}
663+
633664
private void processJVMOkHttpLibrary(final String infrastructureFolder) {
634665
commonJvmMultiplatformSupportingFiles(infrastructureFolder);
635666
addSupportingSerializerAdapters(infrastructureFolder);
@@ -842,8 +873,8 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
842873
});
843874
}
844875

845-
// modify the data type of binary form parameters to a more friendly type for multiplatform builds
846-
if (MULTIPLATFORM.equals(getLibrary()) && operation.allParams != null) {
876+
// modify the data type of binary form parameters to a more friendly type for ktor builds
877+
if ((JVM_KTOR.equals(getLibrary()) || MULTIPLATFORM.equals(getLibrary())) && operation.allParams != null) {
847878
for (CodegenParameter param : operation.allParams) {
848879
if (param.dataFormat != null && param.dataFormat.equals("binary")) {
849880
param.baseType = param.dataType = "io.ktor.client.request.forms.InputProvider";

modules/openapi-generator/src/main/resources/kotlin-client/build.gradle.mustache

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ wrapper {
88

99
buildscript {
1010
ext.kotlin_version = '1.5.10'
11+
{{#jvm-ktor}}
12+
ext.ktor_version = '1.6.7'
13+
{{/jvm-ktor}}
1114
{{#jvm-retrofit2}}
1215
ext.retrofitVersion = '2.9.0'
1316
{{/jvm-retrofit2}}
@@ -81,6 +84,15 @@ dependencies {
8184
{{#kotlinx_serialization}}
8285
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
8386
{{/kotlinx_serialization}}
87+
{{#jvm-ktor}}
88+
implementation "io.ktor:ktor-client-core:$ktor_version"
89+
{{#gson}}
90+
implementation "io.ktor:ktor-client-gson:$ktor_version"
91+
{{/gson}}
92+
{{#jackson}}
93+
implementation "io.ktor:ktor-client-jackson:$ktor_version"
94+
{{/jackson}}
95+
{{/jvm-ktor}}
8496
{{#jvm-okhttp3}}
8597
implementation "com.squareup.okhttp3:okhttp:3.12.13"
8698
{{/jvm-okhttp3}}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{{>licenseInfo}}
2+
package {{apiPackage}}
3+
4+
{{#imports}}import {{import}}
5+
{{/imports}}
6+
7+
import {{packageName}}.infrastructure.*
8+
import io.ktor.client.HttpClientConfig
9+
import io.ktor.client.request.forms.formData
10+
import io.ktor.client.engine.HttpClientEngine
11+
import io.ktor.http.ParametersBuilder
12+
13+
{{#gson}}
14+
import com.google.gson.Gson
15+
{{/gson}}
16+
{{#jackson}}
17+
import com.fasterxml.jackson.databind.ObjectMapper
18+
{{/jackson}}
19+
20+
{{#operations}}
21+
{{#nonPublicApi}}internal {{/nonPublicApi}}open class {{classname}}(
22+
baseUrl: String = ApiClient.BASE_URL,
23+
httpClientEngine: HttpClientEngine? = null,
24+
httpClientConfig: ((HttpClientConfig<*>) -> Unit)? = null,
25+
json: {{#gson}}Gson{{/gson}}{{#jackson}}ObjectMapper{{/jackson}} = ApiClient.JSON_DEFAULT,
26+
) : ApiClient(baseUrl, httpClientEngine, httpClientConfig, json) {
27+
28+
{{#operation}}
29+
/**
30+
* {{summary}}
31+
* {{notes}}
32+
{{#allParams}} * @param {{{paramName}}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
33+
{{/allParams}} * @return {{{returnType}}}{{^returnType}}void{{/returnType}}
34+
*/
35+
{{#returnType}}
36+
@Suppress("UNCHECKED_CAST")
37+
{{/returnType}}
38+
open suspend fun {{operationId}}({{#allParams}}{{{paramName}}}: {{{dataType}}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}}): HttpResponse<{{{returnType}}}{{^returnType}}Unit{{/returnType}}> {
39+
40+
val localVariableAuthNames = listOf<String>({{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}})
41+
42+
val localVariableBody = {{#hasBodyParam}}{{#bodyParam}}{{{paramName}}}{{/bodyParam}}{{/hasBodyParam}}
43+
{{^hasBodyParam}}
44+
{{#hasFormParams}}
45+
{{#isMultipart}}
46+
formData {
47+
{{#formParams}}
48+
{{{paramName}}}?.apply { append("{{{baseName}}}", {{{paramName}}}) }
49+
{{/formParams}}
50+
}
51+
{{/isMultipart}}
52+
{{^isMultipart}}
53+
ParametersBuilder().also {
54+
{{#formParams}}
55+
{{{paramName}}}?.apply { it.append("{{{baseName}}}", {{{paramName}}}.toString()) }
56+
{{/formParams}}
57+
}.build()
58+
{{/isMultipart}}
59+
{{/hasFormParams}}
60+
{{^hasFormParams}}
61+
io.ktor.client.utils.EmptyContent
62+
{{/hasFormParams}}
63+
{{/hasBodyParam}}
64+
65+
val localVariableQuery = mutableMapOf<String, List<String>>()
66+
{{#queryParams}}
67+
{{{paramName}}}?.apply { localVariableQuery["{{baseName}}"] = {{#isContainer}}toMultiValue(this, "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{{paramName}}}"){{/isContainer}} }
68+
{{/queryParams}}
69+
70+
val localVariableHeaders = mutableMapOf<String, String>()
71+
{{#headerParams}}
72+
{{{paramName}}}?.apply { localVariableHeaders["{{baseName}}"] = {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}} }
73+
{{/headerParams}}
74+
75+
val localVariableConfig = RequestConfig<kotlin.Any?>(
76+
RequestMethod.{{httpMethod}},
77+
"{{path}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", "${{{paramName}}}"){{/pathParams}},
78+
query = localVariableQuery,
79+
headers = localVariableHeaders
80+
)
81+
82+
return {{#hasBodyParam}}jsonRequest{{/hasBodyParam}}{{^hasBodyParam}}{{#hasFormParams}}{{#isMultipart}}multipartFormRequest{{/isMultipart}}{{^isMultipart}}urlEncodedFormRequest{{/isMultipart}}{{/hasFormParams}}{{^hasFormParams}}request{{/hasFormParams}}{{/hasBodyParam}}(
83+
localVariableConfig,
84+
localVariableBody,
85+
localVariableAuthNames
86+
).wrap()
87+
}
88+
89+
{{/operation}}
90+
}
91+
{{/operations}}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package {{packageName}}.auth
2+
3+
class ApiKeyAuth(private val location: String, val paramName: String) : Authentication {
4+
var apiKey: String? = null
5+
var apiKeyPrefix: String? = null
6+
7+
override fun apply(query: MutableMap<String, List<String>>, headers: MutableMap<String, String>) {
8+
val key: String = apiKey ?: return
9+
val prefix: String? = apiKeyPrefix
10+
val value: String = if (prefix != null) "$prefix $key" else key
11+
when (location) {
12+
"query" -> query[paramName] = listOf(value)
13+
"header" -> headers[paramName] = value
14+
}
15+
}
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package {{packageName}}.auth
2+
3+
interface Authentication {
4+
5+
/**
6+
* Apply authentication settings to header and query params.
7+
*
8+
* @param query Query parameters.
9+
* @param headers Header parameters.
10+
*/
11+
fun apply(query: MutableMap<String, List<String>>, headers: MutableMap<String, String>)
12+
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package {{packageName}}.auth
2+
3+
import io.ktor.util.InternalAPI
4+
import io.ktor.util.encodeBase64
5+
6+
class HttpBasicAuth : Authentication {
7+
var username: String? = null
8+
var password: String? = null
9+
10+
@OptIn(InternalAPI::class)
11+
override fun apply(query: MutableMap<String, List<String>>, headers: MutableMap<String, String>) {
12+
if (username == null && password == null) return
13+
val str = (username ?: "") + ":" + (password ?: "")
14+
val auth = str.encodeBase64()
15+
headers["Authorization"] = "Basic $auth"
16+
}
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package {{packageName}}.auth
2+
3+
class HttpBearerAuth(private val scheme: String?) : Authentication {
4+
var bearerToken: String? = null
5+
6+
override fun apply(query: MutableMap<String, List<String>>, headers: MutableMap<String, String>) {
7+
val token: String = bearerToken ?: return
8+
headers["Authorization"] = (if (scheme != null) upperCaseBearer(scheme)!! + " " else "") + token
9+
}
10+
11+
private fun upperCaseBearer(scheme: String): String? {
12+
return if ("bearer".equals(scheme, ignoreCase = true)) "Bearer" else scheme
13+
}
14+
}

0 commit comments

Comments
 (0)