Skip to content

Commit

Permalink
[kotlin] Fix the path variable escaping in kotlin client generators (O…
Browse files Browse the repository at this point in the history
  • Loading branch information
CaptainAye authored Oct 22, 2024
1 parent 52610e0 commit eb92eeb
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ import {{packageName}}.infrastructure.*

return RequestConfig(
method = RequestMethod.{{httpMethod}},
path = "{{path}}",
path = "{{{path}}}",
params = params,
query = localVariableQuery,
headers = localVariableHeaders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ import {{packageName}}.infrastructure.*

return RequestConfig(
method = RequestMethod.{{httpMethod}},
path = "{{path}}",
path = "{{{path}}}",
params = params,
query = localVariableQuery,
headers = localVariableHeaders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}},
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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": {
}
}
}
}
}
}
}

0 comments on commit eb92eeb

Please sign in to comment.