Skip to content

fix: correct nullability, SSE, base URL, and dependency defects found by live API testing#41

Merged
lightsofapollo merged 2 commits into
mainfrom
fix/live-tested-generator-defects
Jul 27, 2026
Merged

fix: correct nullability, SSE, base URL, and dependency defects found by live API testing#41
lightsofapollo merged 2 commits into
mainfrom
fix/live-tested-generator-defects

Conversation

@lightsofapollo

Copy link
Copy Markdown
Contributor

Five generator defects, all found by generating a client from RunPod's published v2 OpenAPI document and exercising it against the live API.

Worth stating up front: the published spec was accurate in every case. All 31 collected live response bodies validated clean against it. Every failure was ours. Spec-diffing alone would have reported "all green" indefinitely.

Fixed

OpenAPI 3.1 nullability on required properties (openapi-generator-dsu, P0)

A property both listed in required and declared nullable via type: ["X", "null"] generated a bare T. Such a client compiles, then fails to deserialize the first real response containing null.

GET /v2/catalog/gpus and GET /v2/pods failed outright: GpuType.pool and Pod.template are null in production. Pod.startedAt was the worst latent case — null for any pod that hasn't started, taking out the whole listPods call.

The Option decision (!is_required || prop.nullable) was already correct; prop.nullable was simply never set for this form. Two call sites checked only nullable: true and the anyOf-with-null shape. This bug has now recurred once per nullability spelling added, each time by missing a site, so all three route through a single is_nullable_any() helper. Covers allOf-composed schemas too — RunPod's Pod keeps its required list inside an allOf branch.

SSE operations returned () and hung forever (openapi-generator-x9v, P1)

Operations whose success content is text/event-stream generated -> Result<(), _> and called response.text().await on an open stream, which never returns. With no default timeout on the generated client, this deadlocks the caller's task rather than erroring. Measured hung past 600s.

The streaming signal was already detected in analysis and honored by the server generator; only the client ignored it. These now return impl futures_util::Stream<Item = Result<bytes::Bytes, reqwest::Error>>. The error path still buffers, since error responses are finite.

Scoped deliberately: operations declaring both a JSON body and SSE keep their JSON contract, since those are the stream: true endpoints covered by explicit [streaming] config and changing their return type would break existing callers.

Configured base_url was ignored (openapi-generator-igg)

Worse than originally filed: generated clients started with String::new() regardless, so even users who set [http_client] base_url in TOML got a client that sent every request to a relative path. Now seeded from config, and the CLI resolves servers[0].url into config when the TOML is silent. Explicit config still wins; relative (/v1) and templated ({region}) server URLs are ignored as unusable.

Default(..) error variant was never constructed (openapi-generator-oug)

Declared on every per-operation error enum, constructed zero times — a live 422 whose body parsed cleanly still surfaced as typed: None, so the typed-errors feature silently didn't work for default responses.

Multipart clients didn't compile (openapi-generator-upz)

REQUIRED_DEPS.toml enabled reqwest's multipart feature on reqwest-middleware but not on reqwest, which is pinned default-features = false. Any spec with a file upload — OpenAI's included — produced non-compiling output.

Verification

362 tests pass. One intermediate failure was every_generation_mode_compiles_from_its_exact_dependency_fragment correctly catching that SSE output now requires futures-util; its passing confirms the streaming client compiles from its own declared fragment.

Against the live RunPod API, list_gpu_types and list_pods both failed before and now succeed. These are not trivial passes — the responses contain pool: None on 24 GPU types (alongside real Some("AMPERE_80") values) and template: None on all 8 pods, so the previously-broken path is genuinely exercised.

Not addressed here

Three findings from the same investigation remain open:

  • format: float maps to f32 and loses money precision (live: 0.03 deserializes as 0.029999999329447746). RunPod's billing endpoints use double; catalog prices don't.
  • problem_details() can never succeed against this API's error shape.
  • x-enum-varnames is ignored for parameter enums.

🤖 Generated with Claude Code

Five defects found by live-testing a generated client against the RunPod v2
API. The published spec was accurate in every case; the generator was not.

- 3.1 `type: ["X", "null"]` on a required property generated a non-Option
  field, so clients compiled and then failed on the first response carrying
  null. Consolidates all three nullability spellings behind is_nullable_any so
  a fourth call site cannot drift. (openapi-generator-dsu)
- SSE-only operations returned () and called .text() on an open stream, which
  never returns; the caller's task hung rather than erroring. They now return
  a futures_util::Stream of bytes. Operations declaring both JSON and SSE keep
  their JSON contract. (openapi-generator-x9v)
- Generated clients ignored the configured base_url entirely and started
  empty. Now seeded from config, which the CLI resolves from servers[0].url
  when the TOML is silent. (openapi-generator-igg)
- The Default(..) error variant was declared but never constructed, so a typed
  default-response body surfaced as typed: None. (openapi-generator-nu7)
- Multipart specs enabled reqwest's multipart feature on reqwest-middleware
  but not reqwest, so file-upload clients did not compile. (openapi-generator-upz)

Verified against the live API: list_gpu_types and list_pods both failed before
and now succeed, with pool: None on 24 GPU types and template: None on every
pod confirming the null path is genuinely exercised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openapi-to-rust Ready Ready Preview, Comment Jul 27, 2026 1:15am

Request Review

@lightsofapollo
lightsofapollo merged commit 53ac14a into main Jul 27, 2026
12 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.

1 participant