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