Line data Source code
1 : /// Create a reporter for an error that outputs similar to [`anyhow::Error`] with Display with alternative setting.
2 : ///
3 : /// It can be used with `anyhow::Error` as well.
4 : ///
5 : /// Why would one use this instead of converting to `anyhow::Error` on the spot? Because
6 : /// anyhow::Error would also capture a stacktrace on the spot, which you would later discard after
7 : /// formatting.
8 : ///
9 : /// ## Usage
10 : ///
11 : /// ```rust
12 : /// #[derive(Debug, thiserror::Error)]
13 : /// enum MyCoolError {
14 : /// #[error("should never happen")]
15 : /// Bad(#[source] std::io::Error),
16 : /// }
17 : ///
18 : /// # fn failing_call() -> Result<(), MyCoolError> { Err(MyCoolError::Bad(std::io::ErrorKind::PermissionDenied.into())) }
19 : ///
20 : /// # fn main() {
21 : /// use utils::error::report_compact_sources;
22 : ///
23 : /// if let Err(e) = failing_call() {
24 : /// let e = report_compact_sources(&e);
25 : /// assert_eq!(format!("{e}"), "should never happen: permission denied");
26 : /// }
27 : /// # }
28 : /// ```
29 : ///
30 : /// ## TODO
31 : ///
32 : /// When we are able to describe return position impl trait in traits, this should of course be an
33 : /// extension trait. Until then avoid boxing with this more ackward interface.
34 18 : pub fn report_compact_sources<E: std::error::Error>(e: &E) -> impl std::fmt::Display + '_ {
35 18 : struct AnyhowDisplayAlternateAlike<'a, E>(&'a E);
36 18 :
37 18 : impl<E: std::error::Error> std::fmt::Display for AnyhowDisplayAlternateAlike<'_, E> {
38 18 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 18 : write!(f, "{}", self.0)?;
40 18 :
41 18 : // why is E a generic parameter here? hope that rustc will see through a default
42 18 : // Error::source implementation and leave the following out if there cannot be any
43 18 : // sources:
44 18 : Sources(self.0.source()).try_for_each(|src| write!(f, ": {}", src))
45 18 : }
46 18 : }
47 18 :
48 18 : struct Sources<'a>(Option<&'a (dyn std::error::Error + 'static)>);
49 18 :
50 18 : impl<'a> Iterator for Sources<'a> {
51 18 : type Item = &'a (dyn std::error::Error + 'static);
52 18 :
53 30 : fn next(&mut self) -> Option<Self::Item> {
54 30 : let rem = self.0;
55 30 :
56 30 : let next = self.0.and_then(|x| x.source());
57 30 : self.0 = next;
58 30 : rem
59 30 : }
60 18 : }
61 18 :
62 18 : AnyhowDisplayAlternateAlike(e)
63 18 : }
64 :
65 : #[cfg(test)]
66 : mod tests {
67 : use super::report_compact_sources;
68 :
69 : #[test]
70 6 : fn report_compact_sources_examples() {
71 6 : use std::fmt::Write;
72 6 :
73 36 : #[derive(Debug, thiserror::Error)]
74 6 : enum EvictionError {
75 6 : #[error("cannot evict a remote layer")]
76 6 : CannotEvictRemoteLayer,
77 6 : #[error("stat failed")]
78 6 : StatFailed(#[source] std::io::Error),
79 6 : #[error("layer was no longer part of LayerMap")]
80 6 : LayerNotFound(#[source] anyhow::Error),
81 6 : }
82 6 :
83 6 : let examples = [
84 6 : (
85 6 : line!(),
86 6 : EvictionError::CannotEvictRemoteLayer,
87 6 : "cannot evict a remote layer",
88 6 : ),
89 6 : (
90 6 : line!(),
91 6 : EvictionError::StatFailed(std::io::ErrorKind::PermissionDenied.into()),
92 6 : "stat failed: permission denied",
93 6 : ),
94 6 : (
95 6 : line!(),
96 6 : EvictionError::LayerNotFound(anyhow::anyhow!("foobar")),
97 6 : "layer was no longer part of LayerMap: foobar",
98 6 : ),
99 6 : ];
100 6 :
101 6 : let mut s = String::new();
102 :
103 24 : for (line, example, expected) in examples {
104 18 : s.clear();
105 18 :
106 18 : write!(s, "{}", report_compact_sources(&example)).expect("string grows");
107 18 :
108 18 : assert_eq!(s, expected, "example on line {line}");
109 : }
110 6 : }
111 : }
|