path-to-error
A Serde Deserializer wrapper that reports the exact field path where a deserialization error occurred.
Repository Health
Technical Analysis
serde_path_to_error solves a specific pain point in Rust’s Serde ecosystem: when deserialization fails on a large or deeply nested structure, the default error only names the type-level failure (“invalid type: integer, expected a string”) with no indication of where in the document it happened. This crate wraps any existing Serde Deserializer — JSON, YAML, TOML, or otherwise — and threads a chain of field names, sequence indices, and enum variants through every nested call, so a failed deserialize returns both the original error and a dotted path like dependencies.serde.version.
It is a tiny, dependency-light crate (itoa plus serde_core) built to be dropped into any existing Deserialize call with minimal code change: swap T::deserialize(deserializer) for serde_path_to_error::deserialize(deserializer) and call .path() on the error. It also supports the mirror case for serialization errors via a Serializer wrapper. Maintained by dtolnay (also the author of serde, syn, and anyhow), it is #![no_std]-compatible and has shipped over 260 million downloads on crates.io, making it a de facto standard for surfacing actionable deserialization diagnostics in config loaders, API request validators, and CLI tools built on Serde.
What You Get
- A
deserialize()function that wraps anyDeserializerand returns anError<E>carrying both the original error and a resolvedPath - A
Pathtype withDisplay(dot-separated string likea.b[2].c) and aniter()over individualSegments (map keys, sequence indices, enum variants, or unknown) - A mirrored
Serializerwrapper andserialize()function for locating the source of serialization failures - A low-level
Track/DeserializerAPI for manually instrumented deserializers that don’t go through the top-leveldeserialize()entry point #![no_std]support (viaalloc), so it works in embedded and constrained environments that already use Serde
Common Use Cases
- Reporting precisely which field in a large JSON/YAML/TOML config file failed to parse, instead of a bare type mismatch
- Improving CLI and web API error messages for user-submitted structured input (request bodies, config files, manifests)
- Debugging deeply nested or generated
Deserializestructs during development, where the default serde_json error path is ambiguous - Building developer-facing tooling (linters, schema validators, config editors) that needs to highlight the exact offending key or array index
Under The Hood
Architecture
The crate is organized around a Chain<'a> linked-list enum (src/lib.rs) representing the current position during deserialization — variants for Root, Seq, Map, Struct, Enum, Some, NewtypeStruct/NewtypeVariant, and NonStringKey — each borrowing its parent to avoid heap allocation while walking. A Track struct holds a Cell<Option<Path>> that gets set exactly once, on the first error encountered, via trigger()/trigger_impl(). The wrapping Deserializer (src/de.rs, the largest file at ~1500 lines) implements every Serde visitor callback, pushing a new Chain frame before recursing into nested values and calling Track::trigger when the inner deserializer returns an error. src/path.rs defines the public Path/Segment/Segments types that get built by walking the Chain backwards (Path::from_chain) once an error is known, reversing the collected segments into document order. src/ser.rs mirrors this design for the serialization direction, and src/wrap.rs provides shared plumbing between the two. The design cleanly separates concerns: Chain is an ephemeral, allocation-free traversal structure used during the walk, while Path is the owned, allocated result materialized only once, on failure.
Tech Stack
Pure Rust, edition = "2021", minimum supported Rust version 1.71. Runtime dependencies are minimal: itoa (fast integer-to-string formatting for sequence index segments) and serde_core (the trait-only subset of Serde, used with default-features = false and alloc for #![no_std] compatibility). A cfg(any())-gated dependency on the full serde crate exists purely to keep Cargo.lock resolvable without ever being compiled in. Dev-dependencies (serde, serde_derive, serde_json) are used only for the test suite. CI (.github/workflows/ci.yml) runs the test matrix across nightly/beta/stable/MSRV Rust, a minimal-versions resolution check, and a documentation build, with RUSTFLAGS=-Dwarnings enforced throughout.
Code Quality
The crate has a dedicated tests/ directory with deserialize.rs and serialize.rs integration tests exercising structs, vecs, maps, enums, nested combinations, and non-string map keys against serde_json, each asserting the exact resulting path string. Error handling is explicit and total — every Serde visitor method either delegates or records path context via Track, with no silent fallbacks. The crate carries an extensive, deliberate set of #![allow(clippy::...)] lint suppressions with inline justifications (several linking to known clippy false-positive issues), indicating disciplined, intentional lint management rather than blanket suppression. CI runs the full matrix with warnings-as-errors, plus a nightly run with randomized type layout to catch layout-dependent bugs.
What Makes It Unique
Most Serde error-reporting solutions require pre-generating custom Deserialize impls or switching to a different deserialization crate entirely. serde_path_to_error instead works as a drop-in wrapper around any existing Deserializer implementation with a single call-site change, requiring no macros, no derive changes, and no format-specific integration — it works identically with serde_json, serde_yaml, toml, or a hand-written Deserializer. Its #![no_std] support and near-zero dependency footprint make it viable in contexts (embedded, constrained WASM) where heavier diagnostic tooling isn’t practical, and its allocation-free Chain traversal keeps the happy path cost close to zero.
Used by 2 apps in this directory
ParadeDB
Search · Databases · Analytics
Born out of Y Combinator's S2023 batch, ParadeDB is a Postgres extension that delivers Elasticsearch-quality BM25 search and real-time analytics without a separate search cluster to manage.
Svix
Developer Tools · Automation
Open source, self-hostable webhook infrastructure that handles delivery, retries, HMAC signing, and multi-tenant event management so you never have to build a webhooks system from scratch.