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