Repository navigation
docs(downgrader): OpenAPI Generator Kotlin reads a map of nullable enum $refs as the enum - #245
Conversation
…list $ref limitation - progenitor reads an anyOf with two string-taking branches, the "null" branch counted as one, as a struct that takes only objects. - NSwag's undefined type for a $ref to a oneOf depends on its first branch. - $refs into the items or properties of a multi-type `type` list dangle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…ray/record gap - A $ref into the allOf of a schema that also holds a $ref resolves one member off, since that $ref becomes the first allOf member. - datamodel-code-generator, even with --strict-nullable, leaves null out of an array item or map value that is a nullable array or record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
openapi-zod-client 1.18 drops `nullable: true` from an anyOf branch, as a nullable tuple member or union member becomes; its bundled Swagger Parser rejects openapi 3.1.2, while 3.1.1 reads fine and keeps null. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…y enum and byte getter note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…lable tuple member Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…ull, unions) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…m and byte branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…of z.null()
OpenAPI Generator's PHP client writes no allowed values for the enum of
an array item or map value that is { type: "null" } alone, which the
downgrader writes as { type: "string", nullable: true, enum: [null] },
so it throws on any such array or map that is not empty. Its 3.1 path
lets null through.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…4 arrays Kiota's TypeScript client reads every value of a oneOf of objects with the first branch. In 3.0 a const tag becomes a one-value enum, which the first branch rejects, so a value from any other branch of a Zod discriminated union loses its tag. It also reads an array of format: byte strings, which z.array(z.base64()) becomes, as one byte array and drops it; the C# client throws on such an array. From 3.1 Kiota keeps both as plain strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…e records It keeps null on a nullable record only as an object's property whose values reject null. As a component, an array item, a map value, a branch of an anyOf or oneOf, or with values that may be null, the record's type leaves out null. It reads the 3.1 anyOf with a null branch in all of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…le enums OpenAPI Generator 7.26 merges the oneOf a nullable enum becomes into one enum, and its go-server then checks that property or array item with an AssertstringRequired function it never writes. The existing normalizer flag avoids it. The 3.1 anyOf with a null branch compiles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…on byte and null-only shapes The Spring server, like the Rust client, does not compile a required nullable format: byte property (Arrays.equals on a JsonNullable<byte[]>). It also writes the empty enum the Java clients write for type: null alone, and the Python FastAPI server writes `class X(, Enum)` for such a component. All of them compile from the 3.1 input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…te string with a pattern Zod's z.base64().nullable() becomes a nullable format: byte string that keeps its pattern. OpenAPI Generator 7.26's rust-axum server then checks the pattern with a function that takes a ByteArray, while the field is a Nullable<ByteArray>, so the property does not compile, required or optional. 3.1 input compiles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
… rejects base64 strings A contentEncoding: base64 string, such as Zod's z.base64(), becomes a format: byte string. OpenAPI Generator 7.26's Kotlin client types it as a ByteArray. With serializationLibrary=kotlinx_serialization on a JVM library (jvm-okhttp4, jvm-ktor, jvm-retrofit2, jvm-vertx), no base64 serializer is registered, so kotlinx.serialization reads and writes it as an array of numbers and rejects the base64 string. Moshi registers a base64 adapter, and the multiplatform library uses Base64ByteArray. 3.1 input keeps the string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
…um $refs as the enum A record whose values are a nullable $ref to an enum, such as a z.record of a registered z.enum with .nullable(), becomes an additionalProperties oneOf of the $ref and the "null" branch. OpenAPI Generator 7.26 merges those branches into an inline enum, and its Kotlin client types a map of an inline enum as that enum, so the property becomes the enum and every map is rejected (Moshi and kotlinx.serialization alike). From 3.1 it stays Map<String, E?>. Kotlin 7.26 keeps null for array items, so it moves out of the sentence about leaving null out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance1 warning
Comparing Footnotes
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Kotlin moved out of the null-dropping sentence — the OpenAPI Generator limitations bullet now names only TypeScript and Swift as clients that "leave null out of the type of an array item or map value" that is a nullable
$refor enum. - Added Kotlin map-of-nullable-enum note — in 7.26, OAG's Kotlin client reads a map whose values are a nullable
$refto an enum (e.g.z.recordof a registeredz.enumwith.nullable()) as that enum, rejecting every such map; the caveat now says 3.1 "keeps the null and the map".
The new claim checks out against OpenAPI Generator v7.26.0: kotlin-client/data_class_{req,opt}_var.mustache has no {{#isMap}} branch inside {{#isEnum}}, so a map-of-enum property is typed as the enum and the Map is dropped (upstream issue #25453, open fix #25474). The path runs through the default SIMPLIFY_ONEOF_ANYOF_ENUM merge already documented one bullet above, which turns the nullable $ref into an inline enum. uncheck packages/downgrader/README.md passes.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
…rays or maps of nullable enum $refs (#248) <!-- claude-projects-attribution: {"github_login":"dinwwwh"} --> _Requested by **dinwwwh** · [project thread](https://claude.ai/code/project/chan_01UcKMz91tMarPV1kWLVgLYE?thread=cmsg_01UcKMz91tMarPV1kWLVgLYEAptv6n54e8QDjz18AXHAoD)_ ## Summary Docs only. Extends the README note on array items and map values that are a nullable `$ref` or enum (line 266) with OpenAPI Generator 7.26's `kotlin-server` (Ktor) generator. In 3.0, an array item or map value that is a nullable `$ref` to an enum becomes `oneOf: [{ $ref: E }, { type: string, nullable: true, enum: [null] }]`. Zod writes that for `z.array(E.nullable())` or `z.record(z.string(), E.nullable())` with a registered `E`. The `SIMPLIFY_ONEOF_ANYOF_ENUM` normalizer turns that into an inline enum, and Kotlin's data class template then collapses the container into the enum. This is the same root cause as the Kotlin client note from #245. `kotlin-server` writes: ```kotlin data class NreitemRequest(val v: NreitemRequest.V) { enum class V(val value: kotlin.collections.List<kotlin.String>) { a("a"), b("b"); } } ``` That fails `compileKotlin`: `Argument type mismatch: actual type is 'String', but 'List<String>' was expected` (`Map<String, String>` for the map). Checked: - The 3.1 input compiles for both shapes. - With `--openapi-normalizer SIMPLIFY_ONEOF_ANYOF_ENUM=false`, the 3.0 output compiles too. - Inline nullable enum items and maps fail in 3.1 as well, so the note stays on `$ref`s, as the sentence before it does. - `kotlin-spring` compiles both versions. Found by the code-generator hunt, round cg45. That round added a `kotlin-server` compile check over every test shape: Gradle through the Google Maven mirror, with `AppMain.kt` removed. ## Test plan - [x] `pnpm run check` - [x] Reproduced with OpenAPI Generator 7.26.0 (`gradle compileKotlin`) on the 3.1 input, the downgrader's 3.0 output, and the 3.0 output with the normalizer off --- _Generated by [Claude Code](https://claude.ai/code/session_01VVaw7SHxsMExLa1nyEQJVY)_ --------- Co-authored-by: Claude <noreply@anthropic.com>

Requested by dinwwwh · project thread
Summary
A record whose values are a nullable
$refto an enum, such asz.record(z.string(), Status.nullable())withStatusregistered by.meta({ id }), hasadditionalProperties: { anyOf: [{ $ref }, { type: "null" }] }in 3.1. In 3.0 that becomes aoneOfof the$refand the"null"branch, as designed.OpenAPI Generator 7.26 merges those two branches into an inline enum (the
SIMPLIFY_ONEOF_ANYOF_ENUMnormalizer the README already names). Its Kotlin client then types a map of an inline enum as the enum itself, so the property is generated asval v: Request.Vand not as a map. Every map is rejected, with Moshi and with kotlinx.serialization alike:From 3.1, the same property is
Map<String, E?>and every value round-trips.The README line about OpenAPI Generator leaving null out of array items and map values listed Kotlin. In 7.26, Kotlin keeps null for array items (inline or
$ref), so this PR moves Kotlin out of that sentence and adds one about the map.What was checked
I compiled each shape with
-g kotlin(Gradle) from the 3.1 input and from the 3.0 output, then decoded and re-encoded valid instances with the client's serializer.$refMap<String, E?>, ok$ref$refor inlineWith
--openapi-normalizer SIMPLIFY_ONEOF_ANYOF_ENUM=false, the 3.0 map is kept, but its values become a wrapper class that still rejects every non-null value. So the README doesn't suggest the flag here.Only README text changes.
uncheckpasses.Generated by Claude Code