Skip to content

Commit 27122af

Browse files
authored
fix(kotlin-server): use valid Kotlin String conversions for ktor delegate header params (#24218)
The ktor delegate-pattern header extraction template emitted it.to<fully.qualified.DataType>() (e.g. it.tokotlin.Boolean(), it.tojava.math.BigDecimal()), which is not valid Kotlin and fails to compile. Kotlin's String conversion functions use the simple type name (toBoolean/toInt/toLong/toDouble/toBigDecimal), so map the primitive type flags to those functions instead of interpolating the dataType. Signed-off-by: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com>
1 parent d3ff820 commit 27122af

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{^required}}{{#isEnumOrRef}}?.let { runCatching { {{{dataType}}}.fromValue(it) }.getOrElse { throw BadParameterException(message = "Invalid enum value for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isEnumOrRef}}{{#isNumber}}?.let { runCatching { it.to{{{dataType}}}() }.getOrElse { throw BadParameterException(message = "Invalid number format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{^isString}}{{^isEnumOrRef}}{{^isNumber}}?.let { runCatching { it.to{{{dataType}}}() }.getOrElse { throw BadParameterException(message = "Invalid format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{/isEnumOrRef}}{{/isString}}{{/required}}{{#required}}{{#defaultValue}}{{#isString}}{{/isString}}{{#isEnumOrRef}}?.let { runCatching { {{{dataType}}}.fromValue(it) }.getOrElse { throw BadParameterException(message = "Invalid enum value for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isEnumOrRef}}{{#isNumber}}?.let { runCatching { it.to{{{dataType}}}() }.getOrElse { throw BadParameterException(message = "Invalid number format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{^isString}}{{^isEnumOrRef}}{{^isNumber}}?.let { runCatching { it.to{{{dataType}}}() }.getOrElse { throw BadParameterException(message = "Invalid format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{/isEnumOrRef}}{{/isString}} ?: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}.let { it{{#isString}}{{/isString}}{{#isEnumOrRef}}?.let { runCatching { {{{dataType}}}.fromValue(it) }.getOrElse { throw BadParameterException(message = "Invalid enum value for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isEnumOrRef}}{{#isNumber}}?.let { runCatching { it.to{{{dataType}}}() }.getOrElse { throw BadParameterException(message = "Invalid number format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{^isString}}{{^isEnumOrRef}}{{^isNumber}}?.let { runCatching { it.to{{{dataType}}}() }.getOrElse { throw BadParameterException(message = "Invalid format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{/isEnumOrRef}}{{/isString}} ?: throw BadParameterException(message = "Missing {{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isCookieParam}}cookie{{/isCookieParam}}{{#isFormParam}}form{{/isFormParam}} parameter: {{baseName}}", parameterName = "{{baseName}}", validationType = "required") }{{/defaultValue}}{{/required}}
1+
{{^required}}{{#isEnumOrRef}}?.let { runCatching { {{{dataType}}}.fromValue(it) }.getOrElse { throw BadParameterException(message = "Invalid enum value for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isEnumOrRef}}{{#isNumber}}?.let { runCatching { it.toBigDecimal() }.getOrElse { throw BadParameterException(message = "Invalid number format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{^isString}}{{^isEnumOrRef}}{{^isNumber}}?.let { runCatching { {{#isBoolean}}it.toBoolean(){{/isBoolean}}{{#isInteger}}it.toInt(){{/isInteger}}{{#isLong}}it.toLong(){{/isLong}}{{#isFloat}}it.toFloat(){{/isFloat}}{{#isDouble}}it.toDouble(){{/isDouble}}{{^isBoolean}}{{^isInteger}}{{^isLong}}{{^isFloat}}{{^isDouble}}it.to{{{dataType}}}(){{/isDouble}}{{/isFloat}}{{/isLong}}{{/isInteger}}{{/isBoolean}} }.getOrElse { throw BadParameterException(message = "Invalid format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{/isEnumOrRef}}{{/isString}}{{/required}}{{#required}}{{#defaultValue}}{{#isString}}{{/isString}}{{#isEnumOrRef}}?.let { runCatching { {{{dataType}}}.fromValue(it) }.getOrElse { throw BadParameterException(message = "Invalid enum value for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isEnumOrRef}}{{#isNumber}}?.let { runCatching { it.toBigDecimal() }.getOrElse { throw BadParameterException(message = "Invalid number format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{^isString}}{{^isEnumOrRef}}{{^isNumber}}?.let { runCatching { {{#isBoolean}}it.toBoolean(){{/isBoolean}}{{#isInteger}}it.toInt(){{/isInteger}}{{#isLong}}it.toLong(){{/isLong}}{{#isFloat}}it.toFloat(){{/isFloat}}{{#isDouble}}it.toDouble(){{/isDouble}}{{^isBoolean}}{{^isInteger}}{{^isLong}}{{^isFloat}}{{^isDouble}}it.to{{{dataType}}}(){{/isDouble}}{{/isFloat}}{{/isLong}}{{/isInteger}}{{/isBoolean}} }.getOrElse { throw BadParameterException(message = "Invalid format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{/isEnumOrRef}}{{/isString}} ?: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}.let { it{{#isString}}{{/isString}}{{#isEnumOrRef}}?.let { runCatching { {{{dataType}}}.fromValue(it) }.getOrElse { throw BadParameterException(message = "Invalid enum value for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isEnumOrRef}}{{#isNumber}}?.let { runCatching { it.toBigDecimal() }.getOrElse { throw BadParameterException(message = "Invalid number format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{^isString}}{{^isEnumOrRef}}{{^isNumber}}?.let { runCatching { {{#isBoolean}}it.toBoolean(){{/isBoolean}}{{#isInteger}}it.toInt(){{/isInteger}}{{#isLong}}it.toLong(){{/isLong}}{{#isFloat}}it.toFloat(){{/isFloat}}{{#isDouble}}it.toDouble(){{/isDouble}}{{^isBoolean}}{{^isInteger}}{{^isLong}}{{^isFloat}}{{^isDouble}}it.to{{{dataType}}}(){{/isDouble}}{{/isFloat}}{{/isLong}}{{/isInteger}}{{/isBoolean}} }.getOrElse { throw BadParameterException(message = "Invalid format for parameter {{baseName}}: $it", parameterName = "{{baseName}}") } }{{/isNumber}}{{/isEnumOrRef}}{{/isString}} ?: throw BadParameterException(message = "Missing {{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isCookieParam}}cookie{{/isCookieParam}}{{#isFormParam}}form{{/isFormParam}} parameter: {{baseName}}", parameterName = "{{baseName}}", validationType = "required") }{{/defaultValue}}{{/required}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinServerCodegenTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,48 @@ public void delegatePattern_enumWireValue() throws IOException {
709709
);
710710
}
711711

712+
@Test
713+
public void delegatePattern_headerParamPrimitiveConversion() throws IOException {
714+
// Regression test for https://github.com/OpenAPITools/openapi-generator/issues/24214
715+
// Header params were converted with `it.to<fully.qualified.DataType>()`
716+
// (e.g. `it.tokotlin.Boolean()`, `it.tojava.math.BigDecimal()`), which is not valid Kotlin.
717+
// The correct String extension is `it.to<SimpleName>()`.
718+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
719+
output.deleteOnExit();
720+
721+
KotlinServerCodegen codegen = new KotlinServerCodegen();
722+
codegen.setOutputDir(output.getAbsolutePath());
723+
codegen.additionalProperties().put(LIBRARY, KTOR);
724+
codegen.additionalProperties().put(DELEGATE_PATTERN, true);
725+
726+
new DefaultGenerator().opts(new ClientOptInput()
727+
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue24214-ktor-header-param-conversion.yaml"))
728+
.config(codegen))
729+
.generate();
730+
731+
Path apiPath = Paths.get(output.getAbsolutePath()
732+
+ "/src/main/kotlin/org/openapitools/server/apis/DefaultApi.kt");
733+
734+
assertFileContains(
735+
apiPath,
736+
"val boolHeader = call.request.headers[\"bool-header\"]?.let { runCatching { it.toBoolean() }",
737+
"val intHeader = call.request.headers[\"int-header\"]?.let { runCatching { it.toInt() }",
738+
"val longHeader = call.request.headers[\"long-header\"]?.let { runCatching { it.toLong() }",
739+
"val doubleHeader = call.request.headers[\"double-header\"]?.let { runCatching { it.toDouble() }",
740+
"val numberHeader = call.request.headers[\"number-header\"]?.let { runCatching { it.toBigDecimal() }"
741+
);
742+
743+
// The old, uncompilable fully-qualified conversions must be gone.
744+
assertFileNotContains(
745+
apiPath,
746+
"it.tokotlin.Boolean()",
747+
"it.tokotlin.Int()",
748+
"it.tokotlin.Long()",
749+
"it.tokotlin.Double()",
750+
"it.tojava.math.BigDecimal()"
751+
);
752+
}
753+
712754

713755
@Test
714756
public void testFloatingPointMultipleOfValidationUsesTolerance() throws IOException {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
openapi: 3.0.3
2+
info:
3+
title: ktor header param primitive conversion
4+
version: 1.0.0
5+
paths:
6+
/headers:
7+
get:
8+
operationId: getWithHeaders
9+
parameters:
10+
- name: bool-header
11+
in: header
12+
required: false
13+
schema:
14+
type: boolean
15+
- name: int-header
16+
in: header
17+
required: false
18+
schema:
19+
type: integer
20+
format: int32
21+
- name: long-header
22+
in: header
23+
required: false
24+
schema:
25+
type: integer
26+
format: int64
27+
- name: double-header
28+
in: header
29+
required: false
30+
schema:
31+
type: number
32+
format: double
33+
- name: number-header
34+
in: header
35+
required: false
36+
schema:
37+
type: number
38+
responses:
39+
'200':
40+
description: ok

0 commit comments

Comments
 (0)