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}