mz_ore/error.rs
1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Error utilities.
17
18use std::error::Error;
19use std::fmt::{self, Debug, Display};
20
21/// Extension methods for [`std::error::Error`].
22pub trait ErrorExt: Error {
23 /// Returns a type that displays the error, along with the chain of _source_ errors or
24 /// causes, if there are any.
25 ///
26 /// # Examples
27 ///
28 /// ```
29 /// use anyhow::anyhow;
30 /// use mz_ore::error::ErrorExt;
31 ///
32 /// let error = anyhow!("inner");
33 /// let error = error.context("context");
34 /// assert_eq!(format!("error: ({})", error.display_with_causes()), "error: (context: inner)");
35 /// ```
36 fn display_with_causes(&self) -> ErrorChainFormatter<&Self> {
37 ErrorChainFormatter(self)
38 }
39
40 /// Converts `self` to a string `String`, along with the chain of _source_ errors or
41 /// causes, if there are any.
42 ///
43 /// # Examples
44 ///
45 /// Basic usage:
46 ///
47 /// ```
48 /// use anyhow::anyhow;
49 /// use mz_ore::error::ErrorExt;
50 ///
51 /// let error = anyhow!("inner");
52 /// let error = error.context("context");
53 /// assert_eq!(error.to_string_with_causes(), "context: inner");
54 /// ```
55 fn to_string_with_causes(&self) -> String {
56 format!("{}", self.display_with_causes())
57 }
58}
59
60impl<E: Error + ?Sized> ErrorExt for E {}
61
62/// Formats an error with its full chain of causes.
63pub struct ErrorChainFormatter<E>(E);
64
65impl<E: Error> Display for ErrorChainFormatter<E> {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 Display::fmt(&self.0, f)?;
68
69 let mut maybe_cause = self.0.source();
70
71 while let Some(cause) = maybe_cause {
72 write!(f, ": {}", cause)?;
73 maybe_cause = cause.source();
74 }
75
76 Ok(())
77 }
78}
79
80impl<E: Error> Debug for ErrorChainFormatter<E> {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 Debug::fmt(&self.0, f)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use std::sync::Arc;
89
90 use anyhow::anyhow;
91
92 use super::*;
93
94 #[crate::test]
95 fn basic_usage() {
96 let error = anyhow!("root");
97 let error = error.context("context");
98 assert_eq!(error.to_string_with_causes(), "context: root");
99 }
100
101 #[crate::test]
102 fn basic_usage_with_arc() {
103 // The reason for this signature is that our Plan errors have a `cause` field like this:
104 // ```
105 // cause: Arc<dyn Error + Send + Sync>
106 // ```
107 fn verify(dyn_error: Arc<dyn Error + Send + Sync>) {
108 assert_eq!(
109 dyn_error.to_string_with_causes(),
110 "test error: context: root"
111 );
112 }
113
114 let error = anyhow!("root");
115 let error = error.context("context");
116 let error = TestError { inner: error };
117 let arc_error = Arc::new(error);
118 verify(arc_error);
119 }
120
121 #[derive(Debug)]
122 struct TestError {
123 inner: anyhow::Error,
124 }
125
126 impl Display for TestError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 // We don't print our causes.
129 write!(f, "test error")?;
130 Ok(())
131 }
132 }
133
134 impl std::error::Error for TestError {
135 fn source(&self) -> Option<&(dyn Error + 'static)> {
136 Some(self.inner.as_ref())
137 }
138 }
139}