Skip to content

Commit e79b69b

Browse files
style: rustfmt the nullability regression tests
1 parent fad3ed3 commit e79b69b

2 files changed

Lines changed: 4 additions & 1 deletion

File tree

‎.beads/issues.jsonl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{"id":"openapi-generator-dsu","title":"OpenAPI 3.1 type:[X,null] on required properties generates non-Option field","description":"For OpenAPI 3.1 schemas where a property is BOTH listed in 'required' AND declared nullable via the 3.1 type-array form (type: [\"string\",\"null\"]), the generator emits a non-Option field. Deserialization then fails against real responses that send null.\n\nVERIFIED AGAINST THE LIVE RUNPOD v2 API. Two operations fail outright today:\n- GET /v2/catalog/gpus -\u003e GpuType.pool is null in production; spec says type: [\"string\",\"null\"], required. Generated: 'pub pool: String'.\n- GET /v2/pods -\u003e Pod.template is null in production. Generated: 'pub template: String'.\n\nConfirmed affected fields in https://api.runpod.io/v2/openapi.json (4, walking allOf branches):\n GpuType.pool, Pod.dataCenterId, Pod.template, Pod.startedAt\n\nPod.startedAt is the worst latent case: any pod that has not started returns null, which takes out the entire listPods call for that account.\n\nNote Pod composes via allOf, so the required list and properties live in an allOf branch — a fix must traverse composition, not just top-level component properties.\n\nRoot cause reported by live-testing agent (unverified by me): nullability is computed as is_nullable() || is_nullable_pattern() in src/analysis.rs (~1936-1937, ~2506-2514), covering only 3.0 'nullable: true' and the anyOf-with-null shape. Schema::type_array_contains_null() (src/openapi.rs ~649-656) is called from only one site (analysis.rs ~1555). generator.rs (~2497-2517) then yields a plain String.\n\nClosed issue openapi-generator-bgo fixed only the anyOf case; this type-array case is uncovered.\n\nSeverity: 3.1 is the modern dialect and this silently produces a client that cannot read production responses. This is first-run breakage of the worst kind — it compiles, then fails at runtime.","acceptance_criteria":"A required property declared type: [\"string\",\"null\"] generates Option\u003cString\u003e, including when declared inside an allOf branch. Regression test covers both the direct component-schema case and the allOf-composed case. Generating the RunPod v2 spec yields Option for GpuType.pool, Pod.template, Pod.startedAt, and Pod.dataCenterId.","status":"open","priority":0,"issue_type":"bug","owner":"james@littlebearlabs.io","created_at":"2026-07-26T23:51:03Z","created_by":"James Lal","updated_at":"2026-07-26T23:51:03Z","dependency_count":0,"dependent_count":0,"comment_count":0}
2+
{"id":"openapi-generator-x9v","title":"Client generator ignores SSE detection: streaming ops return () and hang forever","description":"Operations whose response is text/event-stream generate a client method returning Result\u003c(), ApiOpError\u003c..\u003e\u003e. The generated body calls response.text().await on an open SSE stream and discards the result. Since the stream never ends, the call never returns.\n\nVERIFIED: GET /v2/pods/{id}/logs on RunPod v2 generates:\n pub async fn get_pod_logs(..) -\u003e Result\u003c(), ApiOpError\u003cGetPodLogsApiError\u003e\u003e\nThe live-testing agent measured the call hung past 30s and past 600s. The generated HttpClient sets no timeout, so it deadlocks the caller's task rather than erroring. Raw SSE against the same endpoint works and returns well-formed frames.\n\nThe detection already exists and is correct: supports_streaming is computed in src/analysis.rs (~4562-4577) and the SERVER generator honors it (src/server/codegen.rs ~2263-2290). The CLIENT generator never reads it — reported as zero occurrences in client_generator.rs.\n\nTwo defects in one: (1) the streaming contract is dropped, (2) a discarded body plus no default timeout turns it into a hang instead of a visible failure.\n\nMinimum fix: have client_generator consult supports_streaming and emit a streaming return type instead of (). Independently worth doing: give the generated client a default request timeout so a mis-generated call fails loudly rather than hanging.","acceptance_criteria":"An operation declaring a text/event-stream response generates a client method returning a stream type rather than (), and does not call response.text() on it. Covered by a test over a spec with an SSE endpoint.","status":"open","priority":1,"issue_type":"bug","owner":"james@littlebearlabs.io","created_at":"2026-07-26T23:51:19Z","created_by":"James Lal","updated_at":"2026-07-26T23:51:19Z","dependency_count":0,"dependent_count":0,"comment_count":0}
23
{"id":"openapi-generator-upz","title":"Generated multipart clients miss reqwest multipart feature in REQUIRED_DEPS","description":"For specs with multipart/form-data operations, the generator emits client code calling reqwest::multipart::Form and RequestBuilder::multipart(), and correctly adds features=[\"multipart\"] to reqwest-middleware in REQUIRED_DEPS.toml — but NOT to reqwest itself, which is pinned with default-features=false. The generated crate therefore fails to compile with E0433 (cannot find multipart in reqwest) and E0599 (no method named multipart on reqwest_middleware::RequestBuilder when its feature is also absent).\n\nReproducer: generate a client from https://api.studio.nebius.com/openapi.json (Nebius AI Studio, OpenAI-compatible, has POST /v1/files with multipart/form-data), then cargo check. Fails at client.rs with the two errors above.\n\nImpact: hits any spec with a file-upload endpoint, including OpenAI's own spec. This is first-run breakage — the user runs the tool, the output does not compile, and they leave.\n\nFix: when any operation uses multipart/form-data, add \"multipart\" to the reqwest feature list in the emitted REQUIRED_DEPS.toml fragment (alongside the existing reqwest-middleware feature).","acceptance_criteria":"Generating from a spec with a multipart/form-data operation produces a REQUIRED_DEPS.toml whose reqwest entry includes the multipart feature, and the resulting crate compiles clean. Covered by a corpus compile-check.","status":"open","priority":1,"issue_type":"bug","owner":"james@littlebearlabs.io","created_at":"2026-07-26T23:35:12Z","created_by":"James Lal","updated_at":"2026-07-26T23:35:12Z","dependency_count":0,"dependent_count":0,"comment_count":0}
34
{"id":"openapi-generator-8gz","title":"Publish openapi-to-rust 0.10.0","description":"Cut the merged 0.10.0 release from origin/main through the tag-triggered trusted publishing workflow, then verify the workflow, crates.io publication, and GitHub release.","acceptance_criteria":"Annotated tag v0.10.0 points exactly at the fetched origin/main merge; publish workflow passes its verification and trusted publishing jobs; crates.io and GitHub expose v0.10.0.","status":"in_progress","priority":1,"issue_type":"task","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-07-26T23:03:42Z","created_by":"James Lal","updated_at":"2026-07-26T23:03:46Z","started_at":"2026-07-26T23:03:46Z","dependency_count":0,"dependent_count":0,"comment_count":0}
45
{"id":"openapi-generator-1ux","title":"Resolve local response references by JSON pointer","description":"The strict response semantics resolver introduced for 0.10.0 only accepts refs under components.responses. PagerDuty stores a structurally valid Response Object under components.requestBodies and references it from an operation response, causing full corpus generation to fail.","acceptance_criteria":"Local response refs resolve through their actual JSON pointer and deserialize as Response Objects regardless of component namespace; missing, external, structurally incompatible, and cyclic refs fail with actionable errors; PagerDuty parse-only generation and focused response tests pass.","status":"closed","priority":1,"issue_type":"bug","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-07-26T22:45:49Z","created_by":"James Lal","updated_at":"2026-07-26T22:54:46Z","started_at":"2026-07-26T22:45:55Z","closed_at":"2026-07-26T22:54:46Z","close_reason":"Response refs now resolve through validated local JSON pointers with actionable missing/external/incompatible/cycle errors. PagerDuty generate+check passes, all 54 supported specs generate, and full all-features tests plus clippy pass.","dependencies":[{"issue_id":"openapi-generator-1ux","depends_on_id":"openapi-generator-in6","type":"discovered-from","created_at":"2026-07-26T16:45:49Z","created_by":"James Lal","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
@@ -41,6 +42,7 @@
4142
{"id":"openapi-generator-cv4","title":"Support exploded query parameters (GH #27)","description":"GH issue 27: query params with style=form, explode=true (OAS defaults) and object schemas currently map to Option\u003cimpl AsRef\u003cstr\u003e\u003e and are sent as one opaque string. Per OAS/RFC6570 form-explode, each object property must become its own query pair (?color=red). Fix: analyzer synthesizes/resolves a typed struct for object query params with form+explode semantics; client generator emits req.query(\u0026struct) so reqwest/serde_urlencoded serializes properties as individual pairs.","notes":"Implemented on branch issue-27-exploded-query-params, draft PR https://github.com/gpu-cli/openapi-to-rust/pull/28. Close when PR merges. Follow-ups: openapi-generator-anu (deepObject/explode=false/arrays), openapi-generator-0jz (server side).","status":"closed","priority":1,"issue_type":"feature","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-07-12T23:22:24Z","created_by":"James Lal","updated_at":"2026-07-13T04:21:19Z","started_at":"2026-07-12T23:22:49Z","closed_at":"2026-07-13T04:21:19Z","close_reason":"Released in v0.6.0 (PR #28)","dependency_count":0,"dependent_count":0,"comment_count":0}
4243
{"id":"openapi-generator-5q8","title":"DateStrategy::Time emits broken serde codec for time::Date/time::Time (GH #25)","description":"GitHub issue gpu-cli/openapi-to-rust#25: fields with format: date/time under DateStrategy::Time get #[serde(with = \"time::serde::iso8601\")], but that module only supports OffsetDateTime — generated code fails to compile. Fix: emit time::serde::format_description! helper codec modules (time_date_format / time_time_format) into generated code, and correct the time dep requirement features (serde alone doesn't even enable rfc3339).","notes":"Fixed in PR https://github.com/gpu-cli/openapi-to-rust/pull/26 (branch worktree-issue-25-time-date-serde). Close when PR merges.","status":"closed","priority":1,"issue_type":"bug","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-07-11T18:36:28Z","created_by":"James Lal","updated_at":"2026-07-11T18:57:02Z","started_at":"2026-07-11T18:36:45Z","closed_at":"2026-07-11T18:57:02Z","close_reason":"Fixed in PR #26, merged to main, released in v0.5.3","dependency_count":0,"dependent_count":0,"comment_count":0}
4344
{"id":"openapi-generator-dpd","title":"Hybrid string-or-object discriminated unions deserialize-fail","description":"When an anyOf/oneOf union contains a discriminator AND a non-object branch (e.g. string-enum like ToolChoiceOptions), the generator emits a tagged enum that cannot deserialize the string form. Real-world hit: OpenAI ToolChoiceParam returns 'auto' in Response.tool_choice, but generated type is #[serde(tag=\"type\")] enum with no untagged String variant. Need to fall back to #[serde(untagged)] when the union mixes string/scalar branches with tagged-object branches, OR add a String fallback variant before the tagged variants.","notes":"Live repro: `ToolChoiceParam` from openai.yaml line 52518. anyOf has 8 branches; the first (`ToolChoiceOptions`) is a string-enum (\"none\"|\"auto\"|\"required\"), the rest are objects with discriminator propertyName=type. Generator emits `#[serde(tag=\"type\")] enum ToolChoiceParam { ToolChoiceOptions(ToolChoiceOptions), ... }` which cannot deserialize the string \"auto\" because serde tries to read a \"type\" field from a JSON string. Fix: when an anyOf/oneOf branch is a non-object schema (string/number/etc), the generator must emit `#[serde(untagged)]` with the scalar branch first OR add a String variant before the tagged variants. Hit on real OpenAI Responses API `Response.tool_choice` field.","status":"closed","priority":1,"issue_type":"bug","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-05-10T23:47:35Z","created_by":"James Lal","updated_at":"2026-05-11T00:12:46Z","started_at":"2026-05-10T23:54:10Z","closed_at":"2026-05-11T00:12:46Z","close_reason":"Fixed in src/analysis.rs: (dpd) analyze_oneof_union now downgrades to untagged when any branch is non-object — verified live against OpenAI Response.tool_choice='auto' which now deserializes as ToolChoiceParam::ToolChoiceOptions(Auto). (bgo) merge_schema_into_properties now ORs in is_nullable_pattern() for allOf-merged props — verified live against OpenAI Response.incomplete_details which is now Option\u003cResponseIncompleteDetails\u003e and deserializes null cleanly. All 4 smoke tests (OpenAI+Anthropic, sync+stream) pass.","dependency_count":0,"dependent_count":0,"comment_count":0}
45+
{"id":"openapi-generator-oug","title":"Default(ErrorResponse) error variant is generated but never constructed","description":"Every generated per-operation error enum includes a Default(ErrorResponse) variant for the spec's 'default' response, but no code path ever constructs it: 'grep -c \"ApiError::Default(\"' returns 0 in generated clients.\n\nVERIFIED on the RunPod v2 client: Default(ErrorResponse) is declared (types/client around lines 298, 308, 318 of the generated client) and constructed zero times.\n\nConsequence: a live 422 whose body parses cleanly into ErrorResponse still yields typed: None, so callers fall back to raw body strings. The per-operation typed-error feature — one of the generator's headline selling points — silently does not work for default responses.\n\nFound by live-testing the RunPod v2 API against the generated client.","acceptance_criteria":"A response matched only by the spec's 'default' response constructs the Default(..) variant with the typed body, and a test asserts typed is Some for that case.","status":"open","priority":2,"issue_type":"bug","owner":"james@littlebearlabs.io","created_at":"2026-07-26T23:51:30Z","created_by":"James Lal","updated_at":"2026-07-26T23:51:30Z","dependency_count":0,"dependent_count":0,"comment_count":0}
4446
{"id":"openapi-generator-igg","title":"Default client base_url from servers[0].url when config omits it","description":"HttpClient::new() initializes base_url to String::new() when the TOML config has no [http_client] base_url, even when the OpenAPI document declares servers[0].url. Users must read the docs and supply the base URL manually or every request 404s.\n\nObserved generating from https://api.runpod.io/v2/openapi.json, whose spec declares servers[0].url = https://api.runpod.io; the generated client still starts with an empty base_url.\n\nThis matters most for published, generated client crates, where HttpClient::new() working out of the box is the difference between a crate that feels native and one that appears broken on first use.\n\nFix: when [http_client].base_url is absent, fall back to servers[0].url from the spec. Explicit config keeps precedence. Consider also emitting it as a pub const BASE_URL so downstream crates can reference it without hardcoding a string.","acceptance_criteria":"Generating from a spec with a servers entry and no configured base_url yields a client that targets servers[0].url by default; an explicitly configured base_url still wins.","status":"open","priority":2,"issue_type":"feature","owner":"james@littlebearlabs.io","created_at":"2026-07-26T23:35:28Z","created_by":"James Lal","updated_at":"2026-07-26T23:35:28Z","dependency_count":0,"dependent_count":0,"comment_count":0}
4547
{"id":"openapi-generator-tk1","title":"Cache CI scratch compilation targets","description":"Route install-smoke and spec-compile Cargo target directories into paths retained by the existing rust-cache steps so hosted runners reuse compiled dependencies across runs.","acceptance_criteria":"PR and scheduled spec compile jobs use a stable cached scratch target; install-smoke uses a stable cached install target; temporary packaging and generated fixture cleanup remain isolated; workflow syntax and relevant smoke commands pass.","status":"closed","priority":2,"issue_type":"task","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-07-26T22:35:49Z","created_by":"James Lal","updated_at":"2026-07-26T22:40:16Z","started_at":"2026-07-26T22:35:53Z","closed_at":"2026-07-26T22:40:16Z","close_reason":"CI now routes install-smoke and both spec-compile scratch targets through absolute paths under the rust-cache-managed root target tree. actionlint, targeted Anthropic spec compile (offline), and install smoke pass.","dependencies":[{"issue_id":"openapi-generator-tk1","depends_on_id":"openapi-generator-in6","type":"discovered-from","created_at":"2026-07-26T16:35:49Z","created_by":"James Lal","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
4648
{"id":"openapi-generator-in6.4","title":"Detect normalized server tag identifier collisions","description":"Distinct OpenAPI tags such as foo-bar and foo_bar normalize to identical Rust trait/router identifiers and generate duplicate items.","acceptance_criteria":"Server generation rejects or deterministically disambiguates colliding normalized tag identifiers with an actionable diagnostic; regression test covers colliding tags.","status":"closed","priority":2,"issue_type":"bug","assignee":"James Lal","owner":"james@littlebearlabs.io","created_at":"2026-07-26T21:55:43Z","created_by":"James Lal","updated_at":"2026-07-26T22:15:08Z","started_at":"2026-07-26T21:55:54Z","closed_at":"2026-07-26T22:15:08Z","close_reason":"Added deterministic normalized tag collision diagnostics with unit and integration coverage; full suite passes.","dependencies":[{"issue_id":"openapi-generator-in6.4","depends_on_id":"openapi-generator-in6","type":"parent-child","created_at":"2026-07-26T15:55:42Z","created_by":"James Lal","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}

‎tests/nullable_type_array_test.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ fn required_type_array_null_through_allof_is_optional() {
7878
}
7979
});
8080

81-
let result = test_generation("required_type_array_null_allof", spec).expect("Generation failed");
81+
let result =
82+
test_generation("required_type_array_null_allof", spec).expect("Generation failed");
8283

8384
assert!(
8485
result.contains("pub template: Option<String>"),

0 commit comments

Comments
 (0)