Skip to content

docs(downgrader): OpenAPI Generator Spring and FastAPI servers break on byte and null-only shapes - #239

Merged
dinwwwh merged 19 commits into
mainfrom
claude/project-thread-fvev8j
Oct 31, 2026
Merged

dinwwwh merged 19 commits into
mainfrom
claude/project-thread-fvev8j

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 31, 2026

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

Summary

This adds two OpenAPI Generator 7.26 server generators to two existing README lines. Each one compiles from the 3.1 input but not from the downgraded 3.0.

  • Rust line. The spring server also fails to compile a required property that is a nullable format: byte string, such as Zod's z.base64().nullable(). It keeps the value as JsonNullable<byte[]>, and its equals/hashCode call Arrays.equals(JsonNullable<byte[]>, …), which matches no overload. An optional one compiles. The Rust-only clause about optional properties now says "In the Rust client".
  • Java line. The spring server writes the same empty enum the Java clients write for { type: "null" } alone, in a property, an array item or a component. The python-fastapi server writes class Nul(, Enum): for such a component, which is a SyntaxError. A z.null() property or array item there is fine.

How I found it

I compile-checked OpenAPI Generator's server generators on the oRPC Zod, Valibot and ArkType shape catalogs. Each was split into one doc per shape and generated from 3.1 and from the downgraded 3.0. I also ran python-fastapi end to end through FastAPI's TestClient on 112 fuzz and shape docs.

Shape Generator 3.1 3.0
required z.base64().nullable() spring compiles no suitable method found for equals(JsonNullable<byte[]>, …)
optional z.base64().nullable(), array of it, plain z.base64() spring compiles compiles
z.null() property, array item, component spring compiles enum constant expected here
z.null() component (property or array item) python-fastapi runs SyntaxError in class Nul(, Enum):
z.null() inline property or array item python-fastapi runs runs

Other shapes were clean. Once the null-only shapes are removed and safe-integer bounds get format: int64, spring compiles every other catalog shape from 3.0. Without that format, Zod's ±2^53 bounds overflow int in both versions. python-fastapi's only other 3.0-only rejects are tuples, which 3.1 leaves untyped.

OpenAPI Generator's python client writes the same class Nul(, Enum):. I left it out because it fails to generate from the 3.1 doc at all, so the 3.1 input is no way around it there.

Test plan

  • pnpm exec uncheck packages/downgrader/README.md passes.

Generated by Claude Code

claude added 19 commits October 31, 2026 09:23
…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
…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
@dinwwwh dinwwwh self-assigned this Oct 31, 2026
@codecov

codecov Bot commented Oct 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed

codspeed Bot commented Oct 31, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 1 benchmark is dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 29 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing claude/project-thread-fvev8j (9c109e3) with main (e94e73c)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • packages/downgrader/README.md — the two OpenAPI Generator limitation bullets now attribute the nullable format: byte required-property compile failure to the spring server (with the optional-property caveat scoped to the Rust client), and attribute the null-only enum failure to spring and python-fastapi (the latter scoped to a component).

Both additions were checked against OpenAPI Generator v7.26.0 source and both hold: the required nullable format: byte Spring model leaves the field as JsonNullable<byte[]> while pojo.mustache still emits Arrays.equals/Arrays.hashCode (the optional-nullable flag is unset), matching open issue #17935; and the null-only enum renders an empty enum body for spring/Java and class <Name>(, Enum): for a python-fastapi component. uncheck packages/downgrader/README.md passes.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit bab9c34 into main Oct 31, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants