garde/
error.rs

1//! Error types used by `garde`.
2//!
3//! The entrypoint of this module is the [`Error`] type.
4#![allow(dead_code)]
5
6mod rc_list;
7use std::borrow::Cow;
8
9use compact_str::{CompactString, ToCompactString};
10use smallvec::SmallVec;
11
12use self::rc_list::List;
13
14/// A validation error report.
15///
16/// This type is used as a container for errors aggregated during validation.
17/// It is a flat list of `(Path, Error)`.
18/// A single field or list item may have any number of errors attached to it.
19///
20/// It is possible to extract all errors for specific field using the [`select`][`crate::select`] macro.
21#[derive(Clone, Debug)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct Report {
24    errors: Vec<(Path, Error)>,
25}
26
27impl Report {
28    /// Create an empty [`Report`].
29    #[allow(clippy::new_without_default)]
30    pub fn new() -> Self {
31        Self { errors: Vec::new() }
32    }
33
34    /// Append an [`Error`] into this report at the given [`Path`].
35    pub fn append(&mut self, path: Path, error: Error) {
36        self.errors.push((path, error));
37    }
38
39    /// Iterate over all `(Path, Error)` pairs.
40    pub fn iter(&self) -> impl Iterator<Item = &(Path, Error)> {
41        self.errors.iter()
42    }
43
44    /// Returns `true` if the report contains no validation errors.
45    pub fn is_empty(&self) -> bool {
46        self.errors.is_empty()
47    }
48
49    /// Converts into the inner validation errors.
50    pub fn into_inner(self) -> Vec<(Path, Error)> {
51        self.errors
52    }
53}
54
55impl std::fmt::Display for Report {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        for (path, error) in self.iter() {
58            if path.is_empty() {
59                writeln!(f, "{error}")?;
60            } else {
61                writeln!(f, "{path}: {error}")?;
62            }
63        }
64        Ok(())
65    }
66}
67
68impl std::error::Error for Report {}
69
70#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct Error {
73    message: CompactString,
74}
75
76impl Error {
77    pub fn new(message: impl ToCompactString) -> Self {
78        Self {
79            message: message.to_compact_string(),
80        }
81    }
82
83    pub fn message(&self) -> &str {
84        self.message.as_ref()
85    }
86}
87
88impl std::fmt::Display for Error {
89    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
90        write!(f, "{}", self.message)
91    }
92}
93
94impl std::error::Error for Error {}
95
96#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub struct Path {
98    components: List<(Kind, CompactString)>,
99}
100
101#[doc(hidden)]
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
105pub enum Kind {
106    None,
107    Key,
108    Index,
109}
110
111/// Represents a path component without a key. This is useful when the container
112/// only ever holds a single key, which is the case for any 1-tuple struct.
113///
114/// For an example usage, see the implementation of [Inner][`crate::rules::inner::Inner`] for `Option`.
115#[derive(Default)]
116pub struct NoKey(());
117
118impl std::fmt::Display for NoKey {
119    fn fmt(&self, _: &mut std::fmt::Formatter) -> std::fmt::Result {
120        Ok(())
121    }
122}
123
124pub trait PathComponentKind: std::fmt::Display + ToCompactString {
125    fn component_kind() -> Kind;
126}
127
128macro_rules! impl_path_component_kind {
129    ($(@$($G:lifetime)*;)? $T:ty => $which:ident) => {
130        impl $(<$($G),*>)? PathComponentKind for $T {
131            fn component_kind() -> Kind {
132                Kind::$which
133            }
134        }
135    }
136}
137
138impl_path_component_kind!(usize => Index);
139impl_path_component_kind!(@'a; &'a str => Key);
140impl_path_component_kind!(@'a; Cow<'a, str> => Key);
141impl_path_component_kind!(String => Key);
142impl_path_component_kind!(CompactString => Key);
143impl_path_component_kind!(NoKey => None);
144
145impl<T: PathComponentKind> PathComponentKind for &T {
146    fn component_kind() -> Kind {
147        T::component_kind()
148    }
149}
150
151impl Path {
152    pub fn empty() -> Self {
153        Self {
154            components: List::new(),
155        }
156    }
157
158    pub fn len(&self) -> usize {
159        self.components.len()
160    }
161
162    pub fn is_empty(&self) -> bool {
163        self.components.is_empty()
164    }
165
166    pub fn new<C: PathComponentKind>(component: C) -> Self {
167        Self {
168            components: List::new().append((C::component_kind(), component.to_compact_string())),
169        }
170    }
171
172    pub fn join<C: PathComponentKind>(&self, component: C) -> Self {
173        Self {
174            components: self
175                .components
176                .append((C::component_kind(), component.to_compact_string())),
177        }
178    }
179
180    #[doc(hidden)]
181    pub fn __iter(
182        &self,
183    ) -> impl DoubleEndedIterator<Item = (Kind, &CompactString)> + ExactSizeIterator {
184        let mut components = TempComponents::with_capacity(self.components.len());
185        for (kind, component) in self.components.iter() {
186            components.push((*kind, component));
187        }
188        components.into_iter()
189    }
190}
191
192type TempComponents<'a> = SmallVec<[(Kind, &'a CompactString); 8]>;
193
194impl std::fmt::Debug for Path {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        struct Components<'a> {
197            path: &'a Path,
198        }
199
200        impl std::fmt::Debug for Components<'_> {
201            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202                let mut list = f.debug_list();
203                list.entries(self.path.__iter().rev().map(|(_, c)| c))
204                    .finish()
205            }
206        }
207
208        f.debug_struct("Path")
209            .field("components", &Components { path: self })
210            .finish()
211    }
212}
213
214impl std::fmt::Display for Path {
215    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
216        let mut components = self.__iter().rev().peekable();
217        let mut first = true;
218        while let Some((kind, component)) = components.next() {
219            if first && kind == Kind::Index {
220                f.write_str("[")?;
221            }
222            first = false;
223            f.write_str(component.as_str())?;
224            if kind == Kind::Index {
225                f.write_str("]")?;
226            }
227            if let Some((kind, _)) = components.peek() {
228                match kind {
229                    Kind::None => {}
230                    Kind::Key => f.write_str(".")?,
231                    Kind::Index => f.write_str("[")?,
232                }
233            }
234        }
235
236        Ok(())
237    }
238}
239
240#[cfg(feature = "serde")]
241impl serde::Serialize for Path {
242    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
243    where
244        S: serde::Serializer,
245    {
246        use serde::ser::SerializeSeq as _;
247
248        let components = self.__iter().rev();
249        let mut seq = serializer.serialize_seq(Some(components.len()))?;
250        for component in components {
251            seq.serialize_element(&component)?;
252        }
253        seq.end()
254    }
255}
256
257#[cfg(feature = "serde")]
258impl<'de> serde::Deserialize<'de> for Path {
259    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
260    where
261        D: serde::Deserializer<'de>,
262    {
263        let mut components = List::new();
264        for v in SmallVec::<[(Kind, CompactString); 8]>::deserialize(deserializer)? {
265            components = components.append(v);
266        }
267        Ok(Path { components })
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    const _: () = {
276        fn assert<T: Send>() {}
277        let _ = assert::<Report>;
278    };
279
280    #[test]
281    fn path_join() {
282        let path = Path::new("a").join("b").join("c");
283        assert_eq!(path.to_string(), "a.b.c");
284    }
285
286    #[test]
287    fn report_select() {
288        let mut report = Report::new();
289        report.append(Path::new("a").join("b"), Error::new("lol"));
290        report.append(
291            Path::new("a").join("b").join("c"),
292            Error::new("that seems wrong"),
293        );
294        report.append(Path::new("a").join("b").join("c"), Error::new("pog"));
295        report.append(Path::new("array").join("0").join("c"), Error::new("pog"));
296
297        assert_eq!(
298            crate::select!(report, a.b.c).collect::<Vec<_>>(),
299            [&Error::new("that seems wrong"), &Error::new("pog")]
300        );
301
302        assert_eq!(
303            crate::select!(report, array[0].c).collect::<Vec<_>>(),
304            [&Error::new("pog")]
305        );
306    }
307
308    #[cfg(feature = "serde")]
309    mod serde {
310        use super::*;
311
312        #[test]
313        fn roundtrip_serde() {
314            let mut report = Report::new();
315            report.append(Path::new("a").join(0), Error::new("lorem"));
316            report.append(Path::new("a").join(1), Error::new("ispum"));
317            report.append(Path::new("a").join(2), Error::new("dolor"));
318            report.append(Path::new("b").join("c"), Error::new("dolor"));
319
320            let de: Report =
321                serde_json::from_str(&serde_json::to_string(&report).unwrap()).unwrap();
322
323            assert_eq!(report.errors, de.errors);
324        }
325    }
326}