From 0cb5a0d7a1c963fe436d35b2fd232de506635a10 Mon Sep 17 00:00:00 2001 From: Wiebren Braakman Date: Tue, 8 Sep 2026 10:06:53 +0200 Subject: [PATCH] [kotlin] fix: make the enumUnknownDefaultCase moshi fallback adapters null-safe SerializerHelper registers a moshi EnumJsonAdapter with withUnknownFallback per enum, but EnumJsonAdapter is not null-safe and it is registered bare: any model with a nullable enum property throws "value was null! Wrap in .nullSafe() to write nullable values" on a null value, reading and writing alike. The flag traded unknown-value tolerance for a regression on every optional enum field. Append .nullSafe() in both branches. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GcwZ1arjLZNpetHz2a3TJz --- .../SerializerHelper.kt.mustache | 4 +-- .../kotlin/KotlinClientCodegenModelTest.java | 29 +++++++++++++++++++ .../client/infrastructure/SerializerHelper.kt | 10 +++---- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/kotlin-client/jvm-common/infrastructure/SerializerHelper.kt.mustache b/modules/openapi-generator/src/main/resources/kotlin-client/jvm-common/infrastructure/SerializerHelper.kt.mustache index 7e0fb08639d8..3b30175d9945 100644 --- a/modules/openapi-generator/src/main/resources/kotlin-client/jvm-common/infrastructure/SerializerHelper.kt.mustache +++ b/modules/openapi-generator/src/main/resources/kotlin-client/jvm-common/infrastructure/SerializerHelper.kt.mustache @@ -19,7 +19,7 @@ import com.squareup.moshi.adapters.EnumJsonAdapter {{#enumVars}} {{#-last}} .add({{modelPackage}}.{{classname}}::class.java, EnumJsonAdapter.create({{modelPackage}}.{{classname}}::class.java) - .withUnknownFallback({{modelPackage}}.{{classname}}.{{&name}})) + .withUnknownFallback({{modelPackage}}.{{classname}}.{{&name}}).nullSafe()) {{/-last}} {{/enumVars}} {{/allowableValues}} @@ -33,7 +33,7 @@ import com.squareup.moshi.adapters.EnumJsonAdapter {{#enumVars}} {{#-last}} .add({{modelPackage}}.{{classname}}.{{{nameInPascalCase}}}::class.java, EnumJsonAdapter.create({{modelPackage}}.{{classname}}.{{{nameInPascalCase}}}::class.java) - .withUnknownFallback({{modelPackage}}.{{classname}}.{{{nameInPascalCase}}}.{{&name}})) + .withUnknownFallback({{modelPackage}}.{{classname}}.{{{nameInPascalCase}}}.{{&name}}).nullSafe()) {{/-last}} {{/enumVars}} {{/allowableValues}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java index d2972a65bbe9..74af479dda90 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java @@ -988,6 +988,35 @@ public void testJacksonEnumsExposeDecodeOrNullHelper() throws IOException { "Expected exactly one @JsonCreator annotation in the generated enum, found " + jsonCreatorCount); } + @Test + public void testMoshiEnumUnknownDefaultCaseAdaptersAreNullSafe() throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(KOTLIN_GENERATOR) + .setLibrary("jvm-okhttp4") + .setAdditionalProperties(new HashMap<>() {{ + put(CodegenConstants.SERIALIZATION_LIBRARY, "moshi"); + put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true"); + }}) + .setInputSpec("src/test/resources/3_0/enum.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + + generator.opts(clientOptInput).generate(); + + final Path helperKt = Paths.get(output + "/src/main/kotlin/org/openapitools/client/infrastructure/SerializerHelper.kt"); + + // EnumJsonAdapter is not null-safe: registered bare, any model with a nullable enum + // property throws "value was null! Wrap in .nullSafe() to write nullable values" on + // a null value - reading and writing alike - so the flag broke every optional enum field + TestUtils.assertFileContains(helperKt, ".nullSafe())"); + TestUtils.assertFileNotContains(helperKt, "unknown_default_open_api))"); + } + @Test public void testJacksonEnumsWithUnknownDefaultCase() throws IOException { File output = Files.createTempDirectory("test").toFile(); diff --git a/samples/client/petstore/kotlin-enum-default-value/src/main/kotlin/org/openapitools/client/infrastructure/SerializerHelper.kt b/samples/client/petstore/kotlin-enum-default-value/src/main/kotlin/org/openapitools/client/infrastructure/SerializerHelper.kt index 420b3c3c22b8..35b6efb435ba 100644 --- a/samples/client/petstore/kotlin-enum-default-value/src/main/kotlin/org/openapitools/client/infrastructure/SerializerHelper.kt +++ b/samples/client/petstore/kotlin-enum-default-value/src/main/kotlin/org/openapitools/client/infrastructure/SerializerHelper.kt @@ -7,14 +7,14 @@ object SerializerHelper { fun addEnumUnknownDefaultCase(moshiBuilder: Moshi.Builder): Moshi.Builder { return moshiBuilder .add(org.openapitools.client.models.ComplexEnum::class.java, EnumJsonAdapter.create(org.openapitools.client.models.ComplexEnum::class.java) - .withUnknownFallback(org.openapitools.client.models.ComplexEnum.unknown_default_open_api)) + .withUnknownFallback(org.openapitools.client.models.ComplexEnum.unknown_default_open_api).nullSafe()) .add(org.openapitools.client.models.ModelWithEnumPropertyHavingDefault.PropertyName::class.java, EnumJsonAdapter.create(org.openapitools.client.models.ModelWithEnumPropertyHavingDefault.PropertyName::class.java) - .withUnknownFallback(org.openapitools.client.models.ModelWithEnumPropertyHavingDefault.PropertyName.unknown_default_open_api)) + .withUnknownFallback(org.openapitools.client.models.ModelWithEnumPropertyHavingDefault.PropertyName.unknown_default_open_api).nullSafe()) .add(org.openapitools.client.models.PropertyOfDay.DaysOfWeek::class.java, EnumJsonAdapter.create(org.openapitools.client.models.PropertyOfDay.DaysOfWeek::class.java) - .withUnknownFallback(org.openapitools.client.models.PropertyOfDay.DaysOfWeek.unknown_default_open_api)) + .withUnknownFallback(org.openapitools.client.models.PropertyOfDay.DaysOfWeek.unknown_default_open_api).nullSafe()) .add(org.openapitools.client.models.PropertyOfDay.MonthOfYear::class.java, EnumJsonAdapter.create(org.openapitools.client.models.PropertyOfDay.MonthOfYear::class.java) - .withUnknownFallback(org.openapitools.client.models.PropertyOfDay.MonthOfYear.unknown_default_open_api)) + .withUnknownFallback(org.openapitools.client.models.PropertyOfDay.MonthOfYear.unknown_default_open_api).nullSafe()) .add(org.openapitools.client.models.PropertyOfDay.HolidayTypes::class.java, EnumJsonAdapter.create(org.openapitools.client.models.PropertyOfDay.HolidayTypes::class.java) - .withUnknownFallback(org.openapitools.client.models.PropertyOfDay.HolidayTypes.unknown_default_open_api)) + .withUnknownFallback(org.openapitools.client.models.PropertyOfDay.HolidayTypes.unknown_default_open_api).nullSafe()) } }