Skip to content

Commit eb92eeb

Browse files
authored
[kotlin] Fix the path variable escaping in kotlin client generators (#19930) (#19937)
1 parent 52610e0 commit eb92eeb

File tree

8 files changed

+150
-6
lines changed

8 files changed

+150
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
134134

135135
val localVariableConfig = RequestConfig<kotlin.Any?>(
136136
RequestMethod.{{httpMethod}},
137-
"{{path}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", "${{{paramName}}}"){{/pathParams}},
137+
"{{{path}}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", "${{{paramName}}}"){{/pathParams}},
138138
query = localVariableQuery,
139139
headers = localVariableHeaders,
140140
requiresAuthentication = {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ import {{packageName}}.infrastructure.toMultiValue
232232

233233
return RequestConfig(
234234
method = RequestMethod.{{httpMethod}},
235-
path = "{{path}}"{{#pathParams}}.replace("{"+"{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}"+"}", encodeURIComponent({{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{{paramName}}}{{#isEnum}}{{^required}}?{{/required}}.value{{/isEnum}}.toString(){{/isContainer}})){{/pathParams}},
235+
path = "{{{path}}}"{{#pathParams}}.replace("{"+"{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}"+"}", encodeURIComponent({{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{{paramName}}}{{#isEnum}}{{^required}}?{{/required}}.value{{/isEnum}}.toString(){{/isContainer}})){{/pathParams}},
236236
query = localVariableQuery,
237237
headers = localVariableHeaders,
238238
requiresAuthentication = {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ import {{packageName}}.infrastructure.*
133133

134134
return RequestConfig(
135135
method = RequestMethod.{{httpMethod}},
136-
path = "{{path}}",
136+
path = "{{{path}}}",
137137
params = params,
138138
query = localVariableQuery,
139139
headers = localVariableHeaders,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ import {{packageName}}.infrastructure.*
135135

136136
return RequestConfig(
137137
method = RequestMethod.{{httpMethod}},
138-
path = "{{path}}",
138+
path = "{{{path}}}",
139139
params = params,
140140
query = localVariableQuery,
141141
headers = localVariableHeaders,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ import {{packageName}}.infrastructure.*
136136
{{/isDeprecated}}
137137
fun {{operationId}}WithHttpInfo({{#allParams}}{{{paramName}}}: {{#isEnum}}{{#isContainer}}kotlin.collections.List<{{enumName}}{{operationIdCamelCase}}>{{/isContainer}}{{^isContainer}}{{enumName}}{{operationIdCamelCase}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) : Future<ApiResponse<{{#returnType}}{{{returnType}}}?{{/returnType}}{{^returnType}}Unit?{{/returnType}}>> {
138138
val vertxClient = WebClient.create(vertx)
139-
val request = vertxClient.requestAbs(HttpMethod.{{httpMethod}}, UriTemplate.of("$basePath{{path}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", encodeURIComponent({{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{{paramName}}}{{#isEnum}}{{^required}}?{{/required}}.value{{/isEnum}}.toString(){{/isContainer}})){{/pathParams}}))
139+
val request = vertxClient.requestAbs(HttpMethod.{{httpMethod}}, UriTemplate.of("$basePath{{{path}}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", encodeURIComponent({{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{{paramName}}}{{#isEnum}}{{^required}}?{{/required}}.value{{/isEnum}}.toString(){{/isContainer}})){{/pathParams}}))
140140

141141
{{#hasFormParams}}request.putHeader("Content-Type", {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}){{/hasFormParams}}
142142
{{#headerParams}}{{{paramName}}}{{^required}}?{{/required}}.apply { request.putHeader("{{baseName}}", {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}})}{{/headerParams}}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ import kotlinx.serialization.encoding.*
105105

106106
val localVariableConfig = RequestConfig<kotlin.Any?>(
107107
RequestMethod.{{httpMethod}},
108-
"{{path}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{^isEnum}}"${{{paramName}}}"{{/isEnum}}{{#isEnum}}"${ {{paramName}}.value }"{{/isEnum}}{{/isContainer}}){{/pathParams}},
108+
"{{{path}}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{^isEnum}}"${{{paramName}}}"{{/isEnum}}{{#isEnum}}"${ {{paramName}}.value }"{{/isEnum}}{{/isContainer}}){{/pathParams}},
109109
query = localVariableQuery,
110110
headers = localVariableHeaders,
111111
requiresAuthentication = {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}},
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package org.openapitools.codegen.kotlin;
2+
3+
import io.swagger.parser.OpenAPIParser;
4+
import io.swagger.v3.oas.models.OpenAPI;
5+
import io.swagger.v3.parser.core.models.ParseOptions;
6+
import lombok.Getter;
7+
import org.jetbrains.kotlin.com.intellij.openapi.util.text.Strings;
8+
import org.openapitools.codegen.ClientOptInput;
9+
import org.openapitools.codegen.CodegenConstants;
10+
import org.openapitools.codegen.DefaultGenerator;
11+
import org.openapitools.codegen.languages.KotlinClientCodegen;
12+
import org.openapitools.codegen.languages.features.CXFServerFeatures;
13+
import org.testng.annotations.DataProvider;
14+
import org.testng.annotations.Test;
15+
16+
import java.io.File;
17+
import java.io.IOException;
18+
import java.nio.file.Files;
19+
import java.nio.file.Paths;
20+
21+
import static org.openapitools.codegen.TestUtils.assertFileContains;
22+
23+
public class KotlinClientCodegenApiTest {
24+
25+
@DataProvider(name = "pathResponses")
26+
public Object[][] pathResponses() {
27+
return new Object[][]{
28+
{ClientLibrary.JVM_KTOR},
29+
{ClientLibrary.JVM_OKHTTP4},
30+
{ClientLibrary.JVM_SPRING_WEBCLIENT},
31+
{ClientLibrary.JVM_SPRING_RESTCLIENT},
32+
{ClientLibrary.JVM_RETROFIT2},
33+
{ClientLibrary.MULTIPLATFORM},
34+
{ClientLibrary.JVM_VOLLEY},
35+
{ClientLibrary.JVM_VERTX}
36+
};
37+
}
38+
39+
@Test(dataProvider = "pathResponses")
40+
void testPathVariableIsNotEscaped_19930(ClientLibrary library) throws IOException {
41+
42+
OpenAPI openAPI = new OpenAPIParser()
43+
.readLocation("src/test/resources/3_0/kotlin/issue19930-path-escaping.json", null, new ParseOptions()).getOpenAPI();
44+
45+
KotlinClientCodegen codegen = createCodegen(library);
46+
47+
String outputPath = codegen.getOutputDir().replace('\\', '/');
48+
ClientOptInput input = new ClientOptInput();
49+
input.openAPI(openAPI);
50+
input.config(codegen);
51+
52+
DefaultGenerator generator = new DefaultGenerator();
53+
54+
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "false");
55+
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false");
56+
generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false");
57+
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true");
58+
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
59+
60+
generator.opts(input).generate();
61+
62+
System.out.println(outputPath);
63+
64+
assertFileContains(Paths.get(outputPath + "/src/" + library.getSourceRoot() + "/org/openapitools/client/apis/ArticleApi.kt"), "article('{Id}')");
65+
}
66+
67+
private KotlinClientCodegen createCodegen(ClientLibrary library) throws IOException {
68+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
69+
output.deleteOnExit();
70+
KotlinClientCodegen codegen = new KotlinClientCodegen();
71+
codegen.setLibrary(library.getLibraryName());
72+
codegen.setOutputDir(output.getAbsolutePath());
73+
codegen.setSerializationLibrary(library.getSerializationLibrary());
74+
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
75+
codegen.additionalProperties().put(KotlinClientCodegen.USE_SPRING_BOOT3, "true");
76+
codegen.additionalProperties().put(KotlinClientCodegen.DATE_LIBRARY, "kotlinx-datetime");
77+
return codegen;
78+
}
79+
80+
@Getter
81+
private enum ClientLibrary {
82+
JVM_KTOR("main/kotlin"),
83+
JVM_OKHTTP4("main/kotlin"),
84+
JVM_SPRING_WEBCLIENT("main/kotlin"),
85+
JVM_SPRING_RESTCLIENT("main/kotlin"),
86+
JVM_RETROFIT2("main/kotlin"),
87+
MULTIPLATFORM("commonMain/kotlin"),
88+
JVM_VOLLEY("gson", "main/java"),
89+
JVM_VERTX("main/kotlin");
90+
private final String serializationLibrary;
91+
private final String libraryName;
92+
private final String sourceRoot;
93+
94+
ClientLibrary(String serializationLibrary, String sourceRoot) {
95+
this.serializationLibrary = serializationLibrary;
96+
this.sourceRoot = sourceRoot;
97+
this.libraryName = Strings.toLowerCase(this.name()).replace("_", "-");
98+
}
99+
100+
ClientLibrary(String sourceRoot) {
101+
this("jackson", sourceRoot);
102+
}
103+
}
104+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "",
5+
"description": "",
6+
"version": "1.0.0"
7+
},
8+
"servers": [
9+
{
10+
"url": "https://localhost:8080"
11+
}
12+
],
13+
"paths": {
14+
"/article('{Id}')": {
15+
"get": {
16+
"tags": [
17+
"Article"
18+
],
19+
"parameters": [
20+
{
21+
"name": "Id",
22+
"in": "path",
23+
"description": "key: Id of Article",
24+
"required": true,
25+
"schema": {
26+
"type": "string"
27+
}
28+
}
29+
],
30+
"responses": {
31+
"200": {
32+
"description": "Retrieved entity",
33+
"content": {
34+
}
35+
}
36+
}
37+
}
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)