diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache index c6ca0ce0c06..c246003421c 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-ktor/api.mustache @@ -134,7 +134,7 @@ import com.fasterxml.jackson.databind.ObjectMapper val localVariableConfig = RequestConfig( 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}}, diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/api.mustache index 64279439912..bd262322887 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/api.mustache @@ -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}}, diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/api.mustache index 1c533aae70a..e1ad1abe32f 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/api.mustache @@ -133,7 +133,7 @@ import {{packageName}}.infrastructure.* return RequestConfig( method = RequestMethod.{{httpMethod}}, - path = "{{path}}", + path = "{{{path}}}", params = params, query = localVariableQuery, headers = localVariableHeaders, diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-webclient/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-webclient/api.mustache index 55639746351..177a918b06e 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-webclient/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-webclient/api.mustache @@ -135,7 +135,7 @@ import {{packageName}}.infrastructure.* return RequestConfig( method = RequestMethod.{{httpMethod}}, - path = "{{path}}", + path = "{{{path}}}", params = params, query = localVariableQuery, headers = localVariableHeaders, diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-vertx/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-vertx/api.mustache index c84f15f0b3f..1ccca7f298f 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-vertx/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-vertx/api.mustache @@ -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> { 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}} diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/multiplatform/api.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/multiplatform/api.mustache index 249f8edbb63..6566f40d965 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/libraries/multiplatform/api.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/libraries/multiplatform/api.mustache @@ -105,7 +105,7 @@ import kotlinx.serialization.encoding.* val localVariableConfig = RequestConfig( 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}}, diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java new file mode 100644 index 00000000000..4349e88d443 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenApiTest.java @@ -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); + } + } +} diff --git a/modules/openapi-generator/src/test/resources/3_0/kotlin/issue19930-path-escaping.json b/modules/openapi-generator/src/test/resources/3_0/kotlin/issue19930-path-escaping.json new file mode 100644 index 00000000000..3f6ac196203 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/kotlin/issue19930-path-escaping.json @@ -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": { + } + } + } + } + } + } +} \ No newline at end of file