[kotlin] Fix the path variable escaping in kotlin client generators (#19930) (#19937)

This commit is contained in:
Tomasz Letachowicz 2024-10-22 06:34:42 +02:00 committed by GitHub
parent 52610e026e
commit eb92eeb32e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 150 additions and 6 deletions

View File

@ -134,7 +134,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
val localVariableConfig = RequestConfig<kotlin.Any?>(
RequestMethod.{{httpMethod}},
"{{path}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", "${{{paramName}}}"){{/pathParams}},
"{{{path}}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", "${{{paramName}}}"){{/pathParams}},
query = localVariableQuery,
headers = localVariableHeaders,
requiresAuthentication = {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}},

View File

@ -232,7 +232,7 @@ import {{packageName}}.infrastructure.toMultiValue
return RequestConfig(
method = RequestMethod.{{httpMethod}},
path = "{{path}}"{{#pathParams}}.replace("{"+"{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}"+"}", encodeURIComponent({{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{{paramName}}}{{#isEnum}}{{^required}}?{{/required}}.value{{/isEnum}}.toString(){{/isContainer}})){{/pathParams}},
path = "{{{path}}}"{{#pathParams}}.replace("{"+"{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}"+"}", encodeURIComponent({{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{{paramName}}}{{#isEnum}}{{^required}}?{{/required}}.value{{/isEnum}}.toString(){{/isContainer}})){{/pathParams}},
query = localVariableQuery,
headers = localVariableHeaders,
requiresAuthentication = {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}},

View File

@ -133,7 +133,7 @@ import {{packageName}}.infrastructure.*
return RequestConfig(
method = RequestMethod.{{httpMethod}},
path = "{{path}}",
path = "{{{path}}}",
params = params,
query = localVariableQuery,
headers = localVariableHeaders,

View File

@ -135,7 +135,7 @@ import {{packageName}}.infrastructure.*
return RequestConfig(
method = RequestMethod.{{httpMethod}},
path = "{{path}}",
path = "{{{path}}}",
params = params,
query = localVariableQuery,
headers = localVariableHeaders,

View File

@ -136,7 +136,7 @@ import {{packageName}}.infrastructure.*
{{/isDeprecated}}
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}}>> {
val vertxClient = WebClient.create(vertx)
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}}))
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}}))
{{#hasFormParams}}request.putHeader("Content-Type", {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}){{/hasFormParams}}
{{#headerParams}}{{{paramName}}}{{^required}}?{{/required}}.apply { request.putHeader("{{baseName}}", {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}})}{{/headerParams}}

View File

@ -105,7 +105,7 @@ import kotlinx.serialization.encoding.*
val localVariableConfig = RequestConfig<kotlin.Any?>(
RequestMethod.{{httpMethod}},
"{{path}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{^isEnum}}"${{{paramName}}}"{{/isEnum}}{{#isEnum}}"${ {{paramName}}.value }"{{/isEnum}}{{/isContainer}}){{/pathParams}},
"{{{path}}}"{{#pathParams}}.replace("{" + "{{baseName}}" + "}", {{#isContainer}}{{paramName}}.joinToString(","){{/isContainer}}{{^isContainer}}{{^isEnum}}"${{{paramName}}}"{{/isEnum}}{{#isEnum}}"${ {{paramName}}.value }"{{/isEnum}}{{/isContainer}}){{/pathParams}},
query = localVariableQuery,
headers = localVariableHeaders,
requiresAuthentication = {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}},

View File

@ -0,0 +1,104 @@
package org.openapitools.codegen.kotlin;
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.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.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 static org.openapitools.codegen.TestUtils.assertFileContains;
public class KotlinClientCodegenApiTest {
@DataProvider(name = "pathResponses")
public Object[][] pathResponses() {
return new Object[][]{
{ClientLibrary.JVM_KTOR},
{ClientLibrary.JVM_OKHTTP4},
{ClientLibrary.JVM_SPRING_WEBCLIENT},
{ClientLibrary.JVM_SPRING_RESTCLIENT},
{ClientLibrary.JVM_RETROFIT2},
{ClientLibrary.MULTIPLATFORM},
{ClientLibrary.JVM_VOLLEY},
{ClientLibrary.JVM_VERTX}
};
}
@Test(dataProvider = "pathResponses")
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();
KotlinClientCodegen codegen = createCodegen(library);
String outputPath = codegen.getOutputDir().replace('\\', '/');
ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);
DefaultGenerator generator = new DefaultGenerator();
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.opts(input).generate();
System.out.println(outputPath);
assertFileContains(Paths.get(outputPath + "/src/" + library.getSourceRoot() + "/org/openapitools/client/apis/ArticleApi.kt"), "article('{Id}')");
}
private KotlinClientCodegen createCodegen(ClientLibrary library) throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
KotlinClientCodegen codegen = new KotlinClientCodegen();
codegen.setLibrary(library.getLibraryName());
codegen.setOutputDir(output.getAbsolutePath());
codegen.setSerializationLibrary(library.getSerializationLibrary());
codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true");
codegen.additionalProperties().put(KotlinClientCodegen.USE_SPRING_BOOT3, "true");
codegen.additionalProperties().put(KotlinClientCodegen.DATE_LIBRARY, "kotlinx-datetime");
return codegen;
}
@Getter
private enum ClientLibrary {
JVM_KTOR("main/kotlin"),
JVM_OKHTTP4("main/kotlin"),
JVM_SPRING_WEBCLIENT("main/kotlin"),
JVM_SPRING_RESTCLIENT("main/kotlin"),
JVM_RETROFIT2("main/kotlin"),
MULTIPLATFORM("commonMain/kotlin"),
JVM_VOLLEY("gson", "main/java"),
JVM_VERTX("main/kotlin");
private final String serializationLibrary;
private final String libraryName;
private final String sourceRoot;
ClientLibrary(String serializationLibrary, String sourceRoot) {
this.serializationLibrary = serializationLibrary;
this.sourceRoot = sourceRoot;
this.libraryName = Strings.toLowerCase(this.name()).replace("_", "-");
}
ClientLibrary(String sourceRoot) {
this("jackson", sourceRoot);
}
}
}

View File

@ -0,0 +1,40 @@
{
"openapi": "3.0.1",
"info": {
"title": "",
"description": "",
"version": "1.0.0"
},
"servers": [
{
"url": "https://localhost:8080"
}
],
"paths": {
"/article('{Id}')": {
"get": {
"tags": [
"Article"
],
"parameters": [
{
"name": "Id",
"in": "path",
"description": "key: Id of Article",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Retrieved entity",
"content": {
}
}
}
}
}
}
}