error_accumulator/
error.rs

1//! Provide [`AccumulatedError`] to present a collection of errors.
2
3use std::{error::Error, fmt};
4
5use crate::path::SourcePath;
6
7/// A list of recorded errors and their source's path in the input.
8#[derive(Debug, Default)]
9pub struct AccumulatedError {
10    errors: Vec<(SourcePath, Box<dyn Error + Send + Sync + 'static>)>,
11}
12
13impl AccumulatedError {
14    /// Get all accumulated errors of the given type.
15    ///
16    /// Errors are in accumulation order.
17    pub fn get_by_type<E>(&self) -> impl Iterator<Item = (&SourcePath, &E)>
18    where
19        E: Error + Send + Sync + 'static,
20    {
21        self.errors
22            .iter()
23            .filter_map(|(path, stored)| stored.downcast_ref().map(|typed| (path, typed)))
24    }
25
26    /// Get all accumulated errors for a given path.
27    ///
28    /// Errors are in accumulation order.
29    pub fn get_by_path(
30        &self,
31        path: &SourcePath,
32    ) -> impl Iterator<Item = &Box<dyn Error + Send + Sync>> {
33        self.errors
34            .iter()
35            .filter_map(move |(error_path, stored)| (error_path == path).then_some(stored))
36    }
37
38    /// Number of stored errors.
39    pub fn len(&self) -> usize {
40        self.errors.len()
41    }
42
43    /// True if no errors are stored.
44    pub fn is_empty(&self) -> bool {
45        self.errors.is_empty()
46    }
47
48    pub(crate) fn append<E>(&mut self, path: SourcePath, error: E)
49    where
50        E: Error + Send + Sync + 'static,
51    {
52        self.errors.push((path, Box::new(error)));
53    }
54
55    pub(crate) fn merge(&mut self, other: AccumulatedError) {
56        self.errors.extend(other.errors);
57    }
58}
59
60impl fmt::Display for AccumulatedError {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        writeln!(f, "Accumulated errors:")?;
63        for (path, error) in &self.errors {
64            writeln!(f, "- {path}: {error}")?;
65        }
66        Ok(())
67    }
68}
69
70impl Error for AccumulatedError {}
71
72#[cfg(test)]
73mod tests {
74    use std::io;
75
76    use super::*;
77    use crate::{path::PathSegment, test_util::n};
78
79    #[test]
80    fn should_include_path_in_display() {
81        let path1 = SourcePath::new().join(PathSegment::Field(n("foo")));
82        let path2 = SourcePath::new().join(PathSegment::Array {
83            name: n("bar"),
84            index: 2,
85        });
86        let mut error = AccumulatedError::default();
87        error.append(
88            path1.clone(),
89            io::Error::new(io::ErrorKind::Interrupted, "error1"),
90        );
91        error.append(
92            path2.clone(),
93            io::Error::new(io::ErrorKind::AlreadyExists, "error2"),
94        );
95
96        let display = error.to_string();
97        dbg!(&display);
98
99        assert!(display.contains(&path1.to_string()));
100        assert!(display.contains(&path2.to_string()));
101    }
102}