serde_path_to_error/lib.rs
1//! [![github]](https://github.com/dtolnay/path-to-error) [![crates-io]](https://crates.io/crates/serde_path_to_error) [![docs-rs]](https://docs.rs/serde_path_to_error)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! <br>
8//!
9//! Find out the path at which a deserialization error occurred. This crate
10//! provides a wrapper that works with any existing Serde `Deserializer` and
11//! exposes the chain of field names leading to the error.
12//!
13//! # Example
14//!
15//! ```
16//! # use serde_derive::Deserialize;
17//! #
18//! use serde::Deserialize;
19//! use std::collections::BTreeMap as Map;
20//!
21//! #[derive(Deserialize)]
22//! struct Package {
23//! name: String,
24//! dependencies: Map<String, Dependency>,
25//! }
26//!
27//! #[derive(Deserialize)]
28//! struct Dependency {
29//! version: String,
30//! }
31//!
32//! fn main() {
33//! let j = r#"{
34//! "name": "demo",
35//! "dependencies": {
36//! "serde": {
37//! "version": 1
38//! }
39//! }
40//! }"#;
41//!
42//! // Some Deserializer.
43//! let jd = &mut serde_json::Deserializer::from_str(j);
44//!
45//! let result: Result<Package, _> = serde_path_to_error::deserialize(jd);
46//! match result {
47//! Ok(_) => panic!("expected a type error"),
48//! Err(err) => {
49//! let path = err.path().to_string();
50//! assert_eq!(path, "dependencies.serde.version");
51//! }
52//! }
53//! }
54//! ```
55
56#![doc(html_root_url = "https://docs.rs/serde_path_to_error/0.1.17")]
57#![allow(
58 clippy::doc_link_with_quotes, // https://github.com/rust-lang/rust-clippy/issues/8961
59 clippy::elidable_lifetime_names,
60 clippy::iter_not_returning_iterator, // https://github.com/rust-lang/rust-clippy/issues/8285
61 clippy::missing_errors_doc,
62 clippy::module_name_repetitions,
63 clippy::must_use_candidate,
64 clippy::needless_lifetimes,
65 clippy::new_without_default
66)]
67
68mod de;
69mod path;
70mod ser;
71mod wrap;
72
73use std::cell::Cell;
74use std::error::Error as StdError;
75use std::fmt::{self, Display};
76
77pub use crate::de::{deserialize, Deserializer};
78pub use crate::path::{Path, Segment, Segments};
79pub use crate::ser::{serialize, Serializer};
80
81/// Original deserializer error together with the path at which it occurred.
82#[derive(Clone, Debug)]
83pub struct Error<E> {
84 path: Path,
85 original: E,
86}
87
88impl<E> Error<E> {
89 pub fn new(path: Path, inner: E) -> Self {
90 Error {
91 path,
92 original: inner,
93 }
94 }
95
96 /// Element path at which this deserialization error occurred.
97 pub fn path(&self) -> &Path {
98 &self.path
99 }
100
101 /// The Deserializer's underlying error that occurred.
102 pub fn into_inner(self) -> E {
103 self.original
104 }
105
106 /// Reference to the Deserializer's underlying error that occurred.
107 pub fn inner(&self) -> &E {
108 &self.original
109 }
110}
111
112impl<E: Display> Display for Error<E> {
113 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114 if !self.path.is_only_unknown() {
115 write!(f, "{}: ", self.path)?;
116 }
117 write!(f, "{}", self.original)
118 }
119}
120
121impl<E: StdError> StdError for Error<E> {
122 fn source(&self) -> Option<&(dyn StdError + 'static)> {
123 self.original.source()
124 }
125}
126
127/// State for bookkeeping across nested deserializer calls.
128///
129/// You don't need this if you are using `serde_path_to_error::deserializer`. If
130/// you are managing your own `Deserializer`, see the usage example on
131/// [`Deserializer`].
132pub struct Track {
133 path: Cell<Option<Path>>,
134}
135
136impl Track {
137 /// Empty state with no error having happened yet.
138 pub const fn new() -> Self {
139 Track {
140 path: Cell::new(None),
141 }
142 }
143
144 /// Gets path at which the error occurred. Only meaningful after we know
145 /// that an error has occurred. Returns an empty path otherwise.
146 pub fn path(self) -> Path {
147 self.path.into_inner().unwrap_or_else(Path::empty)
148 }
149
150 #[inline]
151 fn trigger<E>(&self, chain: &Chain, err: E) -> E {
152 self.trigger_impl(chain);
153 err
154 }
155
156 fn trigger_impl(&self, chain: &Chain) {
157 self.path.set(Some(match self.path.take() {
158 Some(already_set) => already_set,
159 None => Path::from_chain(chain),
160 }));
161 }
162}
163
164#[derive(Clone)]
165enum Chain<'a> {
166 Root,
167 Seq {
168 parent: &'a Chain<'a>,
169 index: usize,
170 },
171 Map {
172 parent: &'a Chain<'a>,
173 key: String,
174 },
175 Struct {
176 parent: &'a Chain<'a>,
177 key: &'static str,
178 },
179 Enum {
180 parent: &'a Chain<'a>,
181 variant: String,
182 },
183 Some {
184 parent: &'a Chain<'a>,
185 },
186 NewtypeStruct {
187 parent: &'a Chain<'a>,
188 },
189 NewtypeVariant {
190 parent: &'a Chain<'a>,
191 },
192 NonStringKey {
193 parent: &'a Chain<'a>,
194 },
195}