From 9c33737c76ca9bf8617fbc7fa10a8d22a02d38f4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 21:00:12 +0000 Subject: [PATCH 1/2] docs(downgrader): note oapi-codegen stopping on a nullable tagged union typia writes a nullable tagged union as a oneOf with a discriminator mapping beside the null branch. oapi-codegen 2.8.1 wants every branch there other than { type: "null" } to be a $ref, so it fails on our 3.0 null branch with "ambiguous discriminator.mapping". It reads 3.1 too, which keeps { type: "null" }. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VVaw7QuAPaCNmVAw2a3fuB --- packages/downgrader/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/downgrader/README.md b/packages/downgrader/README.md index 94cbb28..7f04feb 100644 --- a/packages/downgrader/README.md +++ b/packages/downgrader/README.md @@ -266,7 +266,7 @@ Accepts the default OAS dialect, which adds `discriminator`, `xml`, `externalDoc - OpenAPI Generator's PHP client drops the namespace separators from the class of an array item that is an `allOf`, as a `$ref` with siblings becomes, writing `\OpenAPIClientModelUser[]`, so deserializing that array throws. OpenAPI Generator reads 3.1 too, which keeps the `$ref` beside its siblings there. - datamodel-code-generator reads a required property with `nullable: true` as never null. Pass `--strict-nullable` to keep null. - aiopenapi3 fails to load a 3.0 document that holds the `"null"` branch: it takes `null` out of a nullable `enum` and builds an empty `Literal` from what is left. aiopenapi3 reads 3.1 too, which keeps `{ type: "null" }`. -- oapi-codegen 2.8 rejects, and the OpenAPI Generator C# client breaks on, a string enum with a `format` such as `uuid`, which a `const` with a `format` becomes, as ArkType writes for `string.uuid`. +- oapi-codegen 2.8 rejects, and the OpenAPI Generator C# client breaks on, a string enum with a `format` such as `uuid`, which a `const` with a `format` becomes, as ArkType writes for `string.uuid`. oapi-codegen also stops on a `oneOf` that holds the `"null"` branch beside a `discriminator` with a `mapping`, as typia writes a nullable tagged union, since it wants every branch other than `{ type: "null" }` there to be a `$ref`. It reads 3.1 too, which keeps `{ type: "null" }` there. - Validators that coerce a value to each branch's `type`, as Prism, express-openapi-validator and openapi-backend do for parameters, and Fastify by default for bodies too, match a value such as `1` or `"1"` to two branches of the `oneOf` a union of scalar types becomes, and reject it. So a query flag that takes `"true"`, `"false"`, `"0"`, `"1"` or a boolean, as GrowthBook writes, rejects `?flag=true`, since `"true"` matches the string and, coerced, the boolean. Fastify by default also wraps a value in an array, so it rejects a single value, and an array of one item, against the `oneOf` a union of a scalar type and an array becomes, as for `string | string[]`. It also coerces in place, so a branch of a `oneOf` can change the value that later branches and the handler see. A number in Effect's `Schema.Number`, a `oneOf` of a number and a string enum, arrives as a string, and null in a nullable enum, a nullable `$ref` to an enum, or a nullable union of literals becomes a value such as `""`, which the `"null"` branch rejects where 3.1's `{ type: "null" }` turns it back into null. Prism, express-openapi-validator, openapi-backend and fastify-openapi-glue read 3.1 too, which keeps the `type` list or the `anyOf`. - Kiota reads the `"null"` branch's `type: "string"` as a real type, so array items that may be null, and a union of several types with null, come out as strings. It also skips a `oneOf` or `anyOf` branch that is an `allOf` holding a `$ref`, as a `$ref` with siblings becomes, so a nullable `$ref` with a `description`, such as Zod's `.describe()` on a schema with its own id before `.nullable()`, comes out as an enum holding only Kiota's null sentinel, and a union of such branches as `UntypedNode` or as its other branches alone. Kiota reads 3.1 too, which keeps their types and the `$ref` beside its siblings. - Speakeasy, like Kiota, reads the `"null"` branch's `type: "string"` as a string, so a nullable `$ref` or enum also takes any string, and a nullable union of scalars with a string becomes `any`, since it then holds two strings. It also fails on a recursive component, reached from another schema, with a property of `type: "null"` alone, such as Zod's `z.null()`. Speakeasy reads 3.1 too, which keeps their types. From d4807b318b5429a106f7efbed6ae17cb8c26b351 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 21:25:04 +0000 Subject: [PATCH 2/2] docs(downgrader): note datamodel-code-generator writing an IntEnum for a mixed enum A 3.1 type list with integer and an enum, such as Litestar's Literal["a", 1], becomes an enum beside an anyOf with an integer branch in 3.0. datamodel-code-generator writes an IntEnum for it, so importing its models fails when the enum holds a string. It reads 3.1 too, which keeps the type list. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VVaw7QuAPaCNmVAw2a3fuB --- packages/downgrader/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/downgrader/README.md b/packages/downgrader/README.md index 7f04feb..207f7c7 100644 --- a/packages/downgrader/README.md +++ b/packages/downgrader/README.md @@ -264,7 +264,7 @@ Accepts the default OAS dialect, which adds `discriminator`, `xml`, `externalDoc - OpenAPI Generator's TypeScript, Swift and Kotlin clients can leave null out of the type of an array item or map value that is a nullable `$ref` or enum, which becomes a `oneOf` with the `"null"` branch. OpenAPI Generator reads 3.1 too, which keeps it. - OpenAPI Generator's Rust client does not compile a required property that is a nullable `format: byte` string, which a nullable `contentEncoding: base64` string becomes, such as Zod's `z.base64().nullable()`. An optional one can also break the optional `format: byte` properties of the models generated after it. OpenAPI Generator reads 3.1 too, which keeps the string. - OpenAPI Generator's PHP client drops the namespace separators from the class of an array item that is an `allOf`, as a `$ref` with siblings becomes, writing `\OpenAPIClientModelUser[]`, so deserializing that array throws. OpenAPI Generator reads 3.1 too, which keeps the `$ref` beside its siblings there. -- datamodel-code-generator reads a required property with `nullable: true` as never null. Pass `--strict-nullable` to keep null. +- datamodel-code-generator reads a required property with `nullable: true` as never null. Pass `--strict-nullable` to keep null. It also writes an `IntEnum` for an `enum` beside an `anyOf` with an `integer` branch, as a `type` list with `integer` and an `enum` becomes, such as Litestar's `Literal["a", 1]`, so its models fail to import when the enum holds a string. It reads 3.1 too, which keeps the `type` list. - aiopenapi3 fails to load a 3.0 document that holds the `"null"` branch: it takes `null` out of a nullable `enum` and builds an empty `Literal` from what is left. aiopenapi3 reads 3.1 too, which keeps `{ type: "null" }`. - oapi-codegen 2.8 rejects, and the OpenAPI Generator C# client breaks on, a string enum with a `format` such as `uuid`, which a `const` with a `format` becomes, as ArkType writes for `string.uuid`. oapi-codegen also stops on a `oneOf` that holds the `"null"` branch beside a `discriminator` with a `mapping`, as typia writes a nullable tagged union, since it wants every branch other than `{ type: "null" }` there to be a `$ref`. It reads 3.1 too, which keeps `{ type: "null" }` there. - Validators that coerce a value to each branch's `type`, as Prism, express-openapi-validator and openapi-backend do for parameters, and Fastify by default for bodies too, match a value such as `1` or `"1"` to two branches of the `oneOf` a union of scalar types becomes, and reject it. So a query flag that takes `"true"`, `"false"`, `"0"`, `"1"` or a boolean, as GrowthBook writes, rejects `?flag=true`, since `"true"` matches the string and, coerced, the boolean. Fastify by default also wraps a value in an array, so it rejects a single value, and an array of one item, against the `oneOf` a union of a scalar type and an array becomes, as for `string | string[]`. It also coerces in place, so a branch of a `oneOf` can change the value that later branches and the handler see. A number in Effect's `Schema.Number`, a `oneOf` of a number and a string enum, arrives as a string, and null in a nullable enum, a nullable `$ref` to an enum, or a nullable union of literals becomes a value such as `""`, which the `"null"` branch rejects where 3.1's `{ type: "null" }` turns it back into null. Prism, express-openapi-validator, openapi-backend and fastify-openapi-glue read 3.1 too, which keeps the `type` list or the `anyOf`.