feat: generate typed tuples for fixed-length positional items (#62) - #64
Merged
Conversation
Positional item schemas — 2020-12 `prefixItems` and the draft-04 `items: [A, B]` spelling — were parsed and then discarded, so every tuple generated `Vec<serde_json::Value>`. gcore said it out loud, in the description directly above the generated field: "First element of the tuple is a key, the second one is its counter value." The length is the load-bearing part. `prefixItems` alone does not cap an array's length — extra elements of any type are legal unless `items: false`, `additionalItems: false`, or `maxItems` says otherwise — and a Rust tuple is fixed-arity, so mapping every `prefixItems` to one would emit code that compiles and then fails on payloads the spec permits. Three tiers instead: 1. length pinned -> a tuple, one element per position; 2. closed, variable length, positions interchangeable -> `Vec<T>`; 3. otherwise -> `Vec<serde_json::Value>`, unchanged. A `$ref` position keeps its named type and an inline object position is hoisted to one, so `analyze_item_schema` now takes the hoist name explicitly rather than deriving it from the parent. Also models 2020-12 boolean schemas for `items`: `items: false` is the canonical way to close a tuple and did not parse at all, failing the whole document the way #60 did. Closes #62 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TD3TSeWKu4VqLtEnRMDjry
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #62. Positional item schemas — 2020-12
prefixItemsand the draft-04items: [A, B]spelling parsed since #60 — were parsed and then discarded, soevery tuple generated
Vec<serde_json::Value>. gcore said it out loud, in thedescription directly above the generated field:
The length is the load-bearing part.
prefixItems: [A, B]does not cap anarray's length — extra elements of any type are legal unless
items: false,additionalItems: false, ormaxItemssays otherwise — and a Rust tuple isfixed-arity. Mapping every
prefixItemsto a tuple would emit code thatcompiles and then fails on payloads the spec permits, which is the failure mode
0.12.3 fixed for nullability. So the analyzer types three tiers:
minItems >= lenand closed) → a tuple, one element perposition.
$refpositions keep their named type; an inline object positionis hoisted to a named type (
{Parent}Item{N}) rather than degrading.Vec<T>,which accepts any permitted length.
Vec<serde_json::Value>, unchanged.Also fixes a parse gap found while implementing this:
items: false— thecanonical 2020-12 way to close a tuple — did not parse, failing the whole
document the way #60 did.
Itemsnow models boolean schemas.Generated compatibility
schema has fixed-length positional items change from
Vec<serde_json::Value>to a tuple; closed homogeneous ones change toVec<T>. Across the 57-spec corpus this touches 7 schemas in 3 specs(
gcore×4 →Vec<(String, i64)>,langsmith×2 →Vec<(String, String)>,opencode×1 →(String, ConfigPluginItem…)). OpenprefixItemsoutput isunchanged.
tuples as JSON arrays, which is the same wire shape as before. A tuple-typed
query parameter degrades to the existing opaque-string path rather than
guessing.
additionalItemsas a "rest" elementtype is still carried but unused; boolean subschemas outside
items(e.g.properties: {a: true}) still fail to parse — filed as Boolean subschemas (true/false) fail to parse outsideitems#63.Validation
tests/tuple_codegen_test.rscovers all three tiers, both spellings, thethree ways to close an array,
$refand inline-object positions, theone-element
(T,)trailing comma, and a named tuple schema aliasing.Two open/variable cases assert the conservative fallback.
snapshot changed — no existing fixture used positional items).
cargo fmt --checkcargo clippy --all-features -- -D warningscargo test --all-featuresRUSTDOCFLAGS=-Dwarnings cargo doc --no-deps --all-featuresscripts/install-smoke.shfor packaging/dependency changes.scripts/spec-compile.shfor generator changes(full 55-spec corpus: 55 passed, 0 gen-failed, 0 check-failed — gcore,
langsmith, and opencode, whose types change, all compile clean).
Generated tuples round-trip real payloads, checked against a compiled scratch
crate rather than only asserted on the generated text:
Notes for reviewers
git diff -wis much smaller than the raw diff: extractinganalyze_item_schemaout of
analyze_array_schemare-indents ~200 lines that are otherwise unchanged.The real change is ~174 lines in
src/analysis.rs.Smallest useful review path:
positional_items_are_closed/positional_items_are_exactinsrc/openapi.rs(the rule), thenanalyze_positional_itemsinsrc/analysis.rs(the tiers), thengenerate_tuple_typeinsrc/generator.rs(the rendering, including theone-element trailing comma).
This warrants a minor release (0.14.0) — generated APIs change for any spec with
fixed-length positional items.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TD3TSeWKu4VqLtEnRMDjry