From 574c31b1521c29859b3e9ebc5e41d0849cf02dd9 Mon Sep 17 00:00:00 2001 From: khayashi4337 Date: Sat, 7 Nov 2026 02:38:21 +0900 Subject: [PATCH 1/3] fix(kotlin-client): keep deepObject wire keys parameter-local and baseName-based Two deepObject bugs in the kotlin-client query serialization: 1. propagateParamBaseNameToVars wrote x-kotlin-param-base-name onto CodegenProperty instances that come from DefaultCodegen.fromProperty()'s cache, so two deepObject parameters sharing a model/property overwrote each other's wire-key prefix. With `filter-a` and `filter-b` sharing one schema, jvm-ktor emitted `localVariableQuery["filter-b[field]"]` for BOTH parameters. Vars are now cloned before the parameter-local extension is attached. 2. jvm-okhttp built the deepObject wire key from paramName (`put("filterA[field]")`), so hyphenated/renamed parameters sent a key that does not match the spec baseName. The template now uses the same x-kotlin-param-base-name extension as jvm-ktor, so the key is `filter-a[field]` regardless of the Kotlin identifier. New fixture kotlin-deepobject-shared-vars.yaml + a jvm-ktor/jvm-okhttp4 test asserting each parameter keeps its own baseName on the wire. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Claude Sonnet 5.5 --- .../languages/KotlinClientCodegen.java | 11 +++++- .../libraries/jvm-okhttp/api.mustache | 6 ++-- .../kotlin/KotlinClientCodegenApiTest.java | 35 +++++++++++++++++++ .../kotlin/kotlin-deepobject-shared-vars.yaml | 31 ++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java index 19f5db5d6078..7afd28b81f90 100755 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java @@ -1378,14 +1378,23 @@ public void postProcessParameter(CodegenParameter parameter) { * `vars` (e.g. jvm-ktor) need the outer baseName to build URL keys like * `paramBaseName[fieldBaseName]` per the OAS deepObject style, and * Mustache provides no access to the outer scope from inside `{{#vars}}`. + * + *

The vars come from {@code DefaultCodegen.fromProperty()}'s cache, so + * the same instance can back multiple parameters; they are cloned before + * the parameter-local extension is attached to avoid leaking one + * parameter's baseName into another's wire keys. */ private void propagateParamBaseNameToVars(CodegenParameter param) { if (!param.isQueryParam || !param.isModel || param.vars == null) { return; } + List vars = new ArrayList<>(param.vars.size()); for (CodegenProperty v : param.vars) { - v.vendorExtensions.put("x-kotlin-param-base-name", param.baseName); + CodegenProperty copy = v.clone(); + copy.vendorExtensions.put("x-kotlin-param-base-name", param.baseName); + vars.add(copy); } + param.vars = vars; } /** 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 8f412c86ee54..35a1e27bd8d0 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 @@ -215,7 +215,7 @@ import {{packageName}}.infrastructure.Serializer {{#isModel}} {{#vars}} if ({{{paramName}}}.{{name}} != null) { - put("{{#isDeepObject}}{{{paramName}}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) + put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{vendorExtensions.x-kotlin-param-base-name}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) } {{/vars}} {{/isModel}} @@ -230,7 +230,7 @@ import {{packageName}}.infrastructure.Serializer {{#isModel}} {{#vars}} if ({{{paramName}}}.{{name}} != null) { - put("{{#isDeepObject}}{{{paramName}}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) + put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{vendorExtensions.x-kotlin-param-base-name}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) } {{/vars}} {{/isModel}} @@ -243,7 +243,7 @@ import {{packageName}}.infrastructure.Serializer {{#isModel}} {{#vars}} if ({{{paramName}}}.{{name}} != null) { - put("{{#isDeepObject}}{{{paramName}}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) + put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{vendorExtensions.x-kotlin-param-base-name}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) } {{/vars}} {{/isModel}} 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 index 51208d4130e8..ee0381827f0d 100644 --- 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 @@ -318,6 +318,41 @@ public void testExplodedObjectQueryParameterJvmOkhttp() throws IOException { "exploded entries must be added after the declared query parameters"); } + @DataProvider(name = "librariesWithDeepObjectWireKeys") + public static Object[][] librariesWithDeepObjectWireKeys() { + return new Object[][]{ + {ClientLibrary.JVM_KTOR}, + {ClientLibrary.JVM_OKHTTP4} + }; + } + + // Two deepObject query parameters share the property instances of the same + // $ref'd schema (fromProperty cache). The per-parameter wire-key prefix must + // stay parameter-local: filter-a[...] must not be overwritten by filter-b's + // propagation. jvm-okhttp prefixes keys with the spec baseName (via + // x-kotlin-param-base-name), not the camelized paramName. + @Test(dataProvider = "librariesWithDeepObjectWireKeys") + public void testDeepObjectParamsKeepOwnWireKeyPrefix(ClientLibrary library) throws IOException { + OpenAPI openAPI = readOpenAPI("src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml"); + + KotlinClientCodegen codegen = createCodegen(library); + DefaultGenerator generator = new DefaultGenerator(); + enableOnlyApiGeneration(generator); + + List files = generator.opts(createClientOptInput(openAPI, codegen)).generate(); + File searchApi = files.stream().filter(file -> file.getName().equals("DefaultApi.kt")).findAny().orElseThrow(); + + if (library == ClientLibrary.JVM_KTOR) { + assertFileContains(searchApi.toPath(), + "localVariableQuery[\"filter-a[field]\"]", + "localVariableQuery[\"filter-b[field]\"]"); + } else { + assertFileContains(searchApi.toPath(), + "put(\"filter-a[field]\"", + "put(\"filter-b[field]\""); + } + } + private static void assertFileContainsLine(List lines, String line) { Assert.assertListContains(lines, s -> s.equals(line), line); } diff --git a/modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml b/modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml new file mode 100644 index 000000000000..2f4b0e321a0a --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.3 +info: + title: kotlin deepObject vars repro + version: 1.0.0 +paths: + /search: + get: + operationId: search + parameters: + - name: filter-a + in: query + style: deepObject + explode: true + schema: + $ref: '#/components/schemas/Filter' + - name: filter-b + in: query + style: deepObject + explode: true + schema: + $ref: '#/components/schemas/Filter' + responses: + '200': + description: ok +components: + schemas: + Filter: + type: object + properties: + field: + type: string From 37768ffdbf29b8c8170ab91681b084631c221153 Mon Sep 17 00:00:00 2001 From: khayashi4337 Date: Wed, 11 Nov 2026 12:23:59 +0900 Subject: [PATCH 2/3] chore(samples): regenerate kotlin-jvm-okhttp echo_api The deepObject wire key now uses the spec baseName (query_object) instead of the camelized paramName (queryObject). Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 --- .../kotlin/org/openapitools/client/apis/QueryApi.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/QueryApi.kt b/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/QueryApi.kt index 28caeef71df9..bbb2fc0a9b58 100644 --- a/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/QueryApi.kt +++ b/samples/client/echo_api/kotlin-jvm-okhttp/src/main/kotlin/org/openapitools/client/apis/QueryApi.kt @@ -400,22 +400,22 @@ open class QueryApi(basePath: kotlin.String = defaultBasePath, client: Call.Fact .apply { if (queryObject != null) { if (queryObject.id != null) { - put("queryObject[id]", listOf(queryObject.id.toString())) + put("query_object[id]", listOf(queryObject.id.toString())) } if (queryObject.name != null) { - put("queryObject[name]", listOf(queryObject.name.toString())) + put("query_object[name]", listOf(queryObject.name.toString())) } if (queryObject.category != null) { - put("queryObject[category]", listOf(queryObject.category.toString())) + put("query_object[category]", listOf(queryObject.category.toString())) } if (queryObject.photoUrls != null) { - put("queryObject[photoUrls]", toMultiValue(queryObject.photoUrls.toList(), "")) + put("query_object[photoUrls]", toMultiValue(queryObject.photoUrls.toList(), "")) } if (queryObject.tags != null) { - put("queryObject[tags]", toMultiValue(queryObject.tags.toList(), "")) + put("query_object[tags]", toMultiValue(queryObject.tags.toList(), "")) } if (queryObject.status != null) { - put("queryObject[status]", listOf(queryObject.status.value)) + put("query_object[status]", listOf(queryObject.status.value)) } } } From f833b5840022ecd2d53e91af70c5872fe2eb0a97 Mon Sep 17 00:00:00 2001 From: khayashi4337 Date: Wed, 11 Nov 2026 15:21:09 +0900 Subject: [PATCH 3/3] fix(kotlin-client): harden deepObject wire-key prefix test and template Address review findings on the shared-vars fix: - Rename the fixture property `field` to `status`: `field` is a Kotlin keyword, so the generated accessor is backticked and `{{name}}` interpolations emit the HTML-escaped `` `field` `` in jvm-okhttp, which cannot compile (a pre-existing template bug, out of scope here). - Assert the generated key/value pairs, not just the keys: the jvm-okhttp4 `put("filter-a[status]", listOf(filterA.status.toString()))` and jvm-ktor `localVariableQuery["filter-a[status]"]` lines now verify that each wire key reads its own parameter's property. - Render the `x-kotlin-param-base-name` prefix with triple braces (like `{{{paramName}}}`) in the three jvm-okhttp put() sites so parameter names containing `'` or `&` are not HTML-escaped in the wire key. The regenerated kotlin-jvm-okhttp-echo-api sample is byte-identical. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 --- .../kotlin-client/libraries/jvm-okhttp/api.mustache | 6 +++--- .../codegen/kotlin/KotlinClientCodegenApiTest.java | 10 ++++++---- .../3_0/kotlin/kotlin-deepobject-shared-vars.yaml | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) 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 35a1e27bd8d0..4bd02a0fde58 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 @@ -215,7 +215,7 @@ import {{packageName}}.infrastructure.Serializer {{#isModel}} {{#vars}} if ({{{paramName}}}.{{name}} != null) { - put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{vendorExtensions.x-kotlin-param-base-name}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) + put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{{vendorExtensions.x-kotlin-param-base-name}}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) } {{/vars}} {{/isModel}} @@ -230,7 +230,7 @@ import {{packageName}}.infrastructure.Serializer {{#isModel}} {{#vars}} if ({{{paramName}}}.{{name}} != null) { - put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{vendorExtensions.x-kotlin-param-base-name}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) + put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{{vendorExtensions.x-kotlin-param-base-name}}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) } {{/vars}} {{/isModel}} @@ -243,7 +243,7 @@ import {{packageName}}.infrastructure.Serializer {{#isModel}} {{#vars}} if ({{{paramName}}}.{{name}} != null) { - put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{vendorExtensions.x-kotlin-param-base-name}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) + put("{{#isDeepObject}}{{#lambda.escapeDollar}}{{{vendorExtensions.x-kotlin-param-base-name}}}{{/lambda.escapeDollar}}[{{/isDeepObject}}{{#lambda.escapeDollar}}{{baseName}}{{/lambda.escapeDollar}}{{#isDeepObject}}]{{/isDeepObject}}", {{#isContainer}}toMultiValue({{{paramName}}}.{{name}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf({{#isDateTime}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDateTime}}{{#isDate}}parseDateToQueryString<{{{dataType}}}>({{{paramName}}}.{{name}}){{/isDate}}{{#isEnum}}{{#isString}}{{{paramName}}}.{{name}}.value{{/isString}}{{^isString}}{{{paramName}}}.{{name}}.toString(){{/isString}}{{/isEnum}}{{^isEnum}}{{^isDateTime}}{{^isDate}}{{{paramName}}}.{{name}}.toString(){{/isDate}}{{/isDateTime}}{{/isEnum}}){{/isContainer}}) } {{/vars}} {{/isModel}} 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 index ee0381827f0d..eb9ae0500bcf 100644 --- 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 @@ -342,14 +342,16 @@ public void testDeepObjectParamsKeepOwnWireKeyPrefix(ClientLibrary library) thro List files = generator.opts(createClientOptInput(openAPI, codegen)).generate(); File searchApi = files.stream().filter(file -> file.getName().equals("DefaultApi.kt")).findAny().orElseThrow(); + // the wire key must be paired with the value of the parameter it names: + // filter-a[status] must read filterA's property, filter-b[status] filterB's if (library == ClientLibrary.JVM_KTOR) { assertFileContains(searchApi.toPath(), - "localVariableQuery[\"filter-a[field]\"]", - "localVariableQuery[\"filter-b[field]\"]"); + "filterA?.status?.let { localVariableQuery[\"filter-a[status]\"] = listOf(\"$it\") }", + "filterB?.status?.let { localVariableQuery[\"filter-b[status]\"] = listOf(\"$it\") }"); } else { assertFileContains(searchApi.toPath(), - "put(\"filter-a[field]\"", - "put(\"filter-b[field]\""); + "put(\"filter-a[status]\", listOf(filterA.status.toString()))", + "put(\"filter-b[status]\", listOf(filterB.status.toString()))"); } } diff --git a/modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml b/modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml index 2f4b0e321a0a..a79d6bc53ea3 100644 --- a/modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-deepobject-shared-vars.yaml @@ -27,5 +27,5 @@ components: Filter: type: object properties: - field: + status: type: string