fix: parse draft-04 tuple items and locate spec parse failures (#60) - #61
Merged
Merged
Conversation
`SchemaDetails.items` only modeled the JSON Schema 2020-12 single-schema form, so a positional array failed the untagged `Schema` enum and took the whole document down. Tooling that predates 2020-12 still emits the draft-04 tuple spelling under `openapi: "3.1.0"` — FastAPI/pydantic v1 does. Model both spellings with an `Items` enum and unify them with `prefixItems` through `SchemaDetails::positional_items`, leaving `item_schema` for the single-schema form. Generated types are unchanged: a tuple generates exactly what `prefixItems` already generated. The embedded Axum validator bundle rewrites the tuple into `prefixItems` for 2020-12, where an array-valued `items` is ignored — without it the declared positions went unchecked at runtime. Refs #60 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TD3TSeWKu4VqLtEnRMDjry
A document that failed to deserialize reported only serde's innermost
message — for the untagged `Schema` enum, "data did not match any variant of
untagged enum Schema" with no field, schema name, or position. Finding the
offending node in a real spec meant bisecting component schemas by hand.
Track the deserialization path with `serde_path_to_error`, then refine it:
serde's own path stops at the first `#[serde(flatten)]` or untagged enum it
buffers through (`#/paths` for an inline schema), so the descent continues
here — through OpenAPI structure to real schema positions, then keyword-first
inside the schema that failed.
Only nodes in a schema position are tested. Inferring from shape does not
work: a `properties` map whose single property is named `properties` fails to
parse as a schema while being perfectly valid, and blaming it would point the
author at the wrong node.
Errors now read:
Failed to parse OpenAPI spec at
#/components/schemas/Body/properties/pair/items:
data did not match any variant of untagged enum Schema
Refs #60
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.
|
3 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
Fixes both halves of #60.
1.
items: [A, B]crashed the parser.SchemaDetails.itemsonly modeled theJSON Schema 2020-12 single-schema form, so the draft-04 positional tuple form
failed the untagged
Schemaenum and took the whole document down. Tooling thatpredates 2020-12 still emits it under
openapi: "3.1.0"— FastAPI/pydantic v1does. Both spellings are now modeled by an
Itemsenum and unified withprefixItemsthroughSchemaDetails::positional_items().2. The error named no node. Parse failures reported only serde's innermost
message —
data did not match any variant of untagged enum Schema— with nofield, schema name, or position, so finding the culprit in a real spec meant
bisecting component schemas by hand. Failures now carry a JSON Pointer:
serde_path_to_errorgives the document-level path, but serde's own trackingstops at the first
#[serde(flatten)]or untagged enum it buffers through(
#/pathsfor an inline schema), so the descent continues inrefine_schema_failure: through OpenAPI structure to real schema positions,then keyword-first inside the schema that failed. Only nodes in a schema
position are tested — inferring from shape does not work, because a
propertiesmap whose single property is named
propertiesfails to parse as a schema whilebeing perfectly valid, and blaming it points the author at the wrong node
(there's a regression test for exactly that shape; github.json contains one).
On a 12 MB spec with a bad node injected deep in a path operation, the refined
pointer is exact and the whole failing run takes 0.7s. Refinement only ever runs
on the error path, and is capped at 20k parse attempts.
Generated compatibility
what
prefixItemsalready generated (Vec<serde_json::Value>plus theminItems/maxItemsdoc comment). Emitting a real(String, String)forfixed-length tuples would be a better result for the users hitting this, but
it changes generated APIs for every existing
prefixItemsspec — left as afollow-up covering both spellings.
Axum validator bundle now rewrites the tuple into
prefixItemsfor 2020-12(and
additionalItemsintoitems). A 2020-12 validator ignores anarray-valued
items, so those declared positions previously went unchecked atruntime; specs using the tuple form now get 422s they should always have got.
serde_path_to_error(no transitive deps); generated crates are unaffected.used for typing (same as
prefixItemsbefore this PR), andadditionalItemsis carried but unused. Line/column in parse errors is not included — YAML
positions are lost in the
serde_yaml::Value→serde_json::Valueconversionand would need a position-preserving parse.
Validation
tests/tuple_items_test.rs: tuple/prefixItemsoutput equivalence, theaccessor unification, the validator bundle rewrite, pointer accuracy for a
component schema and for an inline path schema, and the keyword-named
property false-positive case.
snapshots changed).
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).
Notes for reviewers
Two commits, reviewable independently: the parse fix, then the error locating.
Smallest useful review path:
Itemsand the two accessors insrc/openapi.rs,then
refine_schema_failure/locate_failing_schema/deepest_schema_failureat the bottom of
src/analysis.rs. The five.itemscall sites insrc/analysis.rsare mechanical (&details.items→details.item_schema()).Risk is concentrated in the error-path walk: it is heuristic by nature and can
only ever change what a failing run says, never what a succeeding run
produces.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TD3TSeWKu4VqLtEnRMDjry