Skip to main content

motion_core/
report.rs

1//! Bounded error report that collects every failure of one authoring step.
2
3use crate::{BoxedError, Error};
4use std::{error::Error as StdError, fmt, sync::Arc};
5
6const MAX_REPORTED_ERRORS: usize = 1024;
7
8/// Ordered failures collected while authoring or compiling one value.
9///
10/// A report is the fatal counterpart of [`Diagnostics`](crate::Diagnostics), which carries the
11/// non-fatal warnings of the same step. [`Error`] always describes exactly one failure; fluent
12/// builders retain their deferred failures here and return the whole report from `build`.
13///
14/// At most 1024 errors are kept; later ones are dropped. Reports passed to
15/// [`ErrorReport::record`] are flattened so callers see individual failures. Converting a report
16/// with `?` into [`Error`] keeps it reachable through [`Error::report`].
17///
18/// ```
19/// use motion_core::{Error, ErrorReport};
20/// let mut report = ErrorReport::default();
21/// report.record(Error::from(std::io::Error::other("disk failure")));
22/// assert_eq!(report.len(), 1);
23/// ```
24#[derive(Clone, Debug, Default)]
25pub struct ErrorReport {
26    errors: Vec<Arc<Error>>,
27}
28
29impl ErrorReport {
30    /// Appends one failure, flattening nested reports, until the bound is reached.
31    pub fn record(&mut self, error: Error) {
32        let room = MAX_REPORTED_ERRORS.saturating_sub(self.errors.len());
33        match error {
34            Error::Other(cause) => match cause.downcast::<ErrorReport>() {
35                Ok(report) => self.errors.extend(report.errors.into_iter().take(room)),
36                Err(cause) if room > 0 => self.errors.push(Arc::new(Error::Other(cause))),
37                Err(_) => {}
38            },
39            error if room > 0 => self.errors.push(Arc::new(error)),
40            _ => {}
41        }
42    }
43
44    /// Appends an already shared failure without flattening it.
45    pub fn record_shared(&mut self, error: Arc<Error>) {
46        if self.errors.len() < MAX_REPORTED_ERRORS {
47            self.errors.push(error);
48        }
49    }
50
51    /// Retained failures in insertion order.
52    pub fn errors(&self) -> &[Arc<Error>] {
53        &self.errors
54    }
55
56    /// First retained failure, which classifies the report.
57    pub fn first(&self) -> Option<&Arc<Error>> {
58        self.errors.first()
59    }
60
61    /// Number of retained failures.
62    pub fn len(&self) -> usize {
63        self.errors.len()
64    }
65
66    /// Whether no failure was recorded.
67    pub fn is_empty(&self) -> bool {
68        self.errors.is_empty()
69    }
70
71    /// Takes the retained failures.
72    pub fn into_vec(self) -> Vec<Arc<Error>> {
73        self.errors
74    }
75
76    /// Replaces every retained failure with `map` of it, preserving order.
77    pub fn map(self, map: impl FnMut(Arc<Error>) -> Arc<Error>) -> Self {
78        Self {
79            errors: self.errors.into_iter().map(map).collect(),
80        }
81    }
82}
83
84impl From<Error> for ErrorReport {
85    fn from(error: Error) -> Self {
86        let mut report = Self::default();
87        report.record(error);
88        report
89    }
90}
91
92impl From<crate::ValidationError> for ErrorReport {
93    fn from(error: crate::ValidationError) -> Self {
94        Error::from(error).into()
95    }
96}
97
98impl From<std::io::Error> for ErrorReport {
99    fn from(error: std::io::Error) -> Self {
100        Error::from(error).into()
101    }
102}
103
104impl From<BoxedError> for ErrorReport {
105    fn from(error: BoxedError) -> Self {
106        Error::from(error).into()
107    }
108}
109
110impl From<ErrorReport> for Error {
111    fn from(report: ErrorReport) -> Self {
112        let mut errors = report.errors;
113        if errors.len() == 1 {
114            match Arc::try_unwrap(errors.remove(0)) {
115                Ok(error) => return error,
116                Err(shared) => errors.push(shared),
117            }
118        }
119        Self::Other(Box::new(ErrorReport { errors }))
120    }
121}
122
123impl fmt::Display for ErrorReport {
124    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(formatter, "{} authoring error(s)", self.errors.len())?;
126        for error in &self.errors {
127            write!(formatter, "\n  {error}")?;
128        }
129        Ok(())
130    }
131}
132
133impl StdError for ErrorReport {
134    fn source(&self) -> Option<&(dyn StdError + 'static)> {
135        self.errors
136            .first()
137            .map(|error| error.as_ref() as &(dyn StdError + 'static))
138    }
139}
140
141impl IntoIterator for ErrorReport {
142    type Item = Arc<Error>;
143
144    type IntoIter = std::vec::IntoIter<Arc<Error>>;
145
146    fn into_iter(self) -> Self::IntoIter {
147        self.errors.into_iter()
148    }
149}
150
151impl<'a> IntoIterator for &'a ErrorReport {
152    type Item = &'a Arc<Error>;
153
154    type IntoIter = std::slice::Iter<'a, Arc<Error>>;
155
156    fn into_iter(self) -> Self::IntoIter {
157        self.errors.iter()
158    }
159}