Skip to content

Commit 0da98b0

Browse files
authored
[kotlin-client] Fix enum @JsonCreator to throw for unknown values (#22663)
1 parent a2883f3 commit 0da98b0

File tree

4 files changed

+94
-11
lines changed

4 files changed

+94
-11
lines changed

modules/openapi-generator/src/main/resources/kotlin-client/enum_class.mustache

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,34 @@ import kotlinx.serialization.*
106106
/**
107107
* Returns a valid [{{classname}}] for [data], null otherwise.
108108
*/
109-
{{#jackson}}@JvmStatic
110-
@JsonCreator
111-
{{/jackson}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}fun decode(data: kotlin.Any?): {{classname}}? = data?.let {
109+
{{^jackson}}
110+
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}fun decode(data: kotlin.Any?): {{classname}}? = data?.let {
112111
val normalizedData = "$it".lowercase()
113112
values().firstOrNull { value ->
114113
it == value || normalizedData == "$value".lowercase()
115114
}
116115
}
116+
{{/jackson}}
117+
{{#jackson}}
118+
@JvmStatic
119+
@JsonCreator
120+
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}fun decode(data: kotlin.Any?): {{classname}}{{#isNullable}}?{{/isNullable}} {
121+
if (data == null) {
122+
{{#isNullable}}
123+
return null
124+
{{/isNullable}}
125+
{{^isNullable}}
126+
throw IllegalArgumentException("Value for {{classname}} cannot be null")
127+
{{/isNullable}}
128+
}
129+
val normalizedData = "$data".lowercase()
130+
return values().firstOrNull { value ->
131+
data == value || normalizedData == "$value".lowercase()
132+
}{{#isNullable}}{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}
133+
?: {{#allowableValues}}{{#enumVars}}{{#-last}}{{&name}}{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}
134+
?: throw IllegalArgumentException("Unknown {{classname}} value: $data"){{/enumUnknownDefaultCase}}{{/isNullable}}
135+
}
136+
{{/jackson}}
117137
}
118138
}
119139
{{#kotlinx_serialization}}{{#enumUnknownDefaultCase}}

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,61 @@ public void testJacksonEnumsUseJsonCreator() throws IOException {
714714
TestUtils.assertFileContains(enumKt, "@JsonCreator");
715715
}
716716

717+
@Test
718+
public void testJacksonEnumsThrowForUnknownValue() throws IOException {
719+
File output = Files.createTempDirectory("test").toFile();
720+
output.deleteOnExit();
721+
722+
final CodegenConfigurator configurator = new CodegenConfigurator()
723+
.setGeneratorName("kotlin")
724+
.setLibrary("jvm-retrofit2")
725+
.setAdditionalProperties(new HashMap<>() {{
726+
put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
727+
put(CodegenConstants.MODEL_PACKAGE, "model");
728+
}})
729+
.setInputSpec("src/test/resources/3_0/kotlin/issue22534-kotlin-numeric-enum.yaml")
730+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
731+
732+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
733+
DefaultGenerator generator = new DefaultGenerator();
734+
735+
generator.opts(clientOptInput).generate();
736+
737+
final Path enumKt = Paths.get(output + "/src/main/kotlin/model/ExampleNumericEnum.kt");
738+
739+
// Verify that the decode function throws IllegalArgumentException for unknown values
740+
TestUtils.assertFileContains(enumKt, "throw IllegalArgumentException(\"Unknown ExampleNumericEnum value: $data\")");
741+
}
742+
743+
@Test
744+
public void testJacksonEnumsWithUnknownDefaultCase() throws IOException {
745+
File output = Files.createTempDirectory("test").toFile();
746+
output.deleteOnExit();
747+
748+
final CodegenConfigurator configurator = new CodegenConfigurator()
749+
.setGeneratorName("kotlin")
750+
.setLibrary("jvm-retrofit2")
751+
.setAdditionalProperties(new HashMap<>() {{
752+
put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
753+
put(CodegenConstants.MODEL_PACKAGE, "model");
754+
put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
755+
}})
756+
.setInputSpec("src/test/resources/3_0/kotlin/issue22534-kotlin-numeric-enum.yaml")
757+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
758+
759+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
760+
DefaultGenerator generator = new DefaultGenerator();
761+
762+
generator.opts(clientOptInput).generate();
763+
764+
final Path enumKt = Paths.get(output + "/src/main/kotlin/model/ExampleNumericEnum.kt");
765+
766+
// With enumUnknownDefaultCase=true, should return the default value instead of throwing
767+
TestUtils.assertFileContains(enumKt, "@JsonEnumDefaultValue");
768+
// Should NOT contain throw for unknown values when enumUnknownDefaultCase is enabled
769+
TestUtils.assertFileNotContains(enumKt, "throw IllegalArgumentException(\"Unknown ExampleNumericEnum value");
770+
}
771+
717772
@Test(description = "convert an empty model to object")
718773
public void emptyModelKotlinxSerializationTest() throws IOException {
719774
final Schema<?> schema = new ObjectSchema()

samples/client/echo_api/kotlin-jvm-spring-3-restclient/src/main/kotlin/org/openapitools/client/models/StringEnumRef.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ enum class StringEnumRef(@get:JsonValue val value: kotlin.String) {
6262
*/
6363
@JvmStatic
6464
@JsonCreator
65-
fun decode(data: kotlin.Any?): StringEnumRef? = data?.let {
66-
val normalizedData = "$it".lowercase()
67-
values().firstOrNull { value ->
68-
it == value || normalizedData == "$value".lowercase()
65+
fun decode(data: kotlin.Any?): StringEnumRef {
66+
if (data == null) {
67+
throw IllegalArgumentException("Value for StringEnumRef cannot be null")
6968
}
69+
val normalizedData = "$data".lowercase()
70+
return values().firstOrNull { value ->
71+
data == value || normalizedData == "$value".lowercase()
72+
}
73+
?: unknown_default_open_api
7074
}
7175
}
7276
}

samples/client/echo_api/kotlin-jvm-spring-3-webclient/src/main/kotlin/org/openapitools/client/models/StringEnumRef.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ enum class StringEnumRef(@get:JsonValue val value: kotlin.String) {
6262
*/
6363
@JvmStatic
6464
@JsonCreator
65-
fun decode(data: kotlin.Any?): StringEnumRef? = data?.let {
66-
val normalizedData = "$it".lowercase()
67-
values().firstOrNull { value ->
68-
it == value || normalizedData == "$value".lowercase()
65+
fun decode(data: kotlin.Any?): StringEnumRef {
66+
if (data == null) {
67+
throw IllegalArgumentException("Value for StringEnumRef cannot be null")
6968
}
69+
val normalizedData = "$data".lowercase()
70+
return values().firstOrNull { value ->
71+
data == value || normalizedData == "$value".lowercase()
72+
}
73+
?: unknown_default_open_api
7074
}
7175
}
7276
}

0 commit comments

Comments
 (0)