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 3 : pub fn report_compact_sources<E: std::error::Error>(e: &E) -> impl std::fmt::Display + '_ {
35 : struct AnyhowDisplayAlternateAlike<'a, E>(&'a E);
36 :
37 : impl<E: std::error::Error> std::fmt::Display for AnyhowDisplayAlternateAlike<'_, E> {
38 3 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 3 : write!(f, "{}", self.0)?;
40 :
41 : // why is E a generic parameter here? hope that rustc will see through a default
42 : // Error::source implementation and leave the following out if there cannot be any
43 : // sources:
44 3 : Sources(self.0.source()).try_for_each(|src| write!(f, ": {}", src))
45 3 : }
46 : }
47 :
48 : struct Sources<'a>(Option<&'a (dyn std::error::Error + 'static)>);
49 :
50 : impl<'a> Iterator for Sources<'a> {
51 : type Item = &'a (dyn std::error::Error + 'static);
52 :
53 5 : fn next(&mut self) -> Option<Self::Item> {
54 5 : let rem = self.0;
55 5 :
56 5 : let next = self.0.and_then(|x| x.source());
57 5 : self.0 = next;
58 5 : rem
59 5 : }
60 : }
61 :
62 3 : AnyhowDisplayAlternateAlike(e)
63 3 : }
64 :
65 : #[cfg(test)]
66 : mod tests {
67 : use super::report_compact_sources;
68 :
69 : #[test]
70 1 : fn report_compact_sources_examples() {
71 : use std::fmt::Write;
72 :
73 6 : #[derive(Debug, thiserror::Error)]
74 : enum EvictionError {
75 : #[error("cannot evict a remote layer")]
76 : CannotEvictRemoteLayer,
77 : #[error("stat failed")]
78 : StatFailed(#[source] std::io::Error),
79 : #[error("layer was no longer part of LayerMap")]
80 : LayerNotFound(#[source] anyhow::Error),
81 : }
82 :
83 1 : let examples = [
84 1 : (
85 1 : line!(),
86 1 : EvictionError::CannotEvictRemoteLayer,
87 1 : "cannot evict a remote layer",
88 1 : ),
89 1 : (
90 1 : line!(),
91 1 : EvictionError::StatFailed(std::io::ErrorKind::PermissionDenied.into()),
92 1 : "stat failed: permission denied",
93 1 : ),
94 1 : (
95 1 : line!(),
96 1 : EvictionError::LayerNotFound(anyhow::anyhow!("foobar")),
97 1 : "layer was no longer part of LayerMap: foobar",
98 1 : ),
99 1 : ];
100 1 :
101 1 : let mut s = String::new();
102 :
103 4 : for (line, example, expected) in examples {
104 3 : s.clear();
105 3 :
106 3 : write!(s, "{}", report_compact_sources(&example)).expect("string grows");
107 3 :
108 3 : assert_eq!(s, expected, "example on line {line}");
109 : }
110 1 : }
111 : }
|