Skip to content

fix(generator): make Anthropic + OpenAI clients compile + add CI regression guard#16

Merged
lightsofapollo merged 1 commit into
mainfrom
feat/spec-compile-ci
May 8, 2026
Merged

fix(generator): make Anthropic + OpenAI clients compile + add CI regression guard#16
lightsofapollo merged 1 commit into
mainfrom
feat/spec-compile-ci

Conversation

@lightsofapollo
Copy link
Copy Markdown
Contributor

Summary

Generating the Anthropic and OpenAI clients with the post-#15 generator surfaced four real-world codegen bugs introduced by the conformance batch. This PR fixes them and locks the regression in with a new CI job.

Bugs fixed

  1. Optional request bodies didn't typecheck. T11 wraps non-required bodies in Option<T>, but generate_request_body was still chaining .multipart(form) / .body(...) directly off the request builder, which doesn't compile when the value is Option<Form>. The fix moves body application out of the chain into if let Some(...) { req = req.body(...) }.

  2. $ref-typed parameters with non-enum targets. T10 made get_param_rust_type honor schema_ref always — but for object refs (e.g. OpenAI's Metadata wrapper struct) that emitted (Metadata).to_string(), which doesn't compile because structs don't implement Display. The fix is at analysis time: only keep schema_ref on a parameter when the referenced component is a string with enum or const. Struct refs fall back to String until we have proper deepObject / form serialization (T14).

  3. String enums had no Display/AsRef<str>. Query/path/header emitters call .as_ref() and .to_string() on enum-typed parameters; the generated enum needs both impls. generate_string_enum now emits as_str(), Display, and AsRef<str> impls that return the serde-renamed wire form.

  4. rust_type == "String" was the wrong gate. After T10, the gate that decides between .as_ref().to_string() and .to_string() needs to also check schema_ref. Introduced param_uses_as_ref_str() as a single source of truth.

Regression guard (CI)

New scripts/spec-compile.sh and a spec-compile GitHub Actions job:

  • Generates clients for specs/anthropic.yaml and specs/openai.yaml.
  • Synthesises a tiny scratch crate around each (reqwest 0.12 + reqwest-middleware 0.4 w/ multipart feature + thiserror).
  • Runs cargo check on both.

Any generator change that emits invalid Rust against these reference specs now fails the PR. The script is also developer-runnable: scripts/spec-compile.sh (or a subset like scripts/spec-compile.sh anthropic).

Adding more specs in the future is one line in SPECS=(...) inside the script.

Test plan

  • cargo test --tests — 205/205 pass
  • cargo clippy --all-features -- -D warnings — clean
  • cargo fmt --check — clean
  • scripts/spec-compile.sh — both Anthropic and OpenAI compile cleanly locally
  • CI spec-compile job passes (will verify on push)

🤖 Generated with Claude Code

Regenerating the clients for the Anthropic and OpenAI specs against the
post-conformance generator surfaced four codegen bugs introduced by the
recent batch:

1. Optional request bodies (T11) were chained as
   `req.post(url).multipart(form)` even when `form: Option<Form>`. The
   chain doesn't typecheck for optional bodies.
   Fix: generate_request_body now emits statements that mutate `req`,
   wrapped in `if let Some(...)` for optional bodies.

2. $ref-typed parameters (T10) emitted `(StructType).to_string()` for
   non-enum schema refs (e.g. `Metadata` — a wrapper struct). Structs
   don't implement Display.
   Fix: at analysis time, only keep schema_ref on a parameter when the
   referenced component is a string-with-enum/const. Struct refs fall
   back to `String` until proper deepObject/form serialization (T14).

3. The previous fix exposed a second issue: enum-typed query params
   called `.as_ref()` on the enum value, but generated string enums
   didn't implement AsRef<str> or Display.
   Fix: generate_string_enum now emits `as_str()`, `Display`, and
   `AsRef<str>` impls so the enum's serde-renamed wire form drops
   straight into headers, query strings, and path segments.

4. Query/path/header emitters used `param.rust_type == "String"` to
   decide whether to call `.as_ref()` before `.to_string()`. After T10,
   that's wrong for enum-typed params (rust_type stays "String" but
   schema_ref carries the enum name).
   Fix: param_uses_as_ref_str() helper consults schema_ref too.

Verified via the new scripts/spec-compile.sh: regenerates clients for
specs/anthropic.yaml and specs/openai.yaml and runs `cargo check` on
both — both compile cleanly.

ci(spec-compile): run scripts/spec-compile.sh in a new CI job

Regression guard: any generator change that emits invalid Rust against
the Anthropic or OpenAI specs fails the PR. Unit tests exercise the
generator surface but can pass while real-world specs break — this
catches that gap.

chore: ignore /tmp/{spec-compile,gen-*}/ build artifacts

Refs #14

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lightsofapollo lightsofapollo merged commit 22b29d9 into main May 8, 2026
5 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