1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file at the
// root of this repository, or online at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Error utilities.

use std::error::Error;
use std::fmt::{self, Debug, Display};

/// Extension methods for [`std::error::Error`].
pub trait ErrorExt: Error {
    /// Returns a type that displays the error, along with the chain of _source_ errors or
    /// causes, if there are any.
    ///
    /// # Examples
    ///
    /// ```
    /// use anyhow::anyhow;
    /// use mz_ore::error::ErrorExt;
    ///
    /// let error = anyhow!("inner");
    /// let error = error.context("context");
    /// assert_eq!(format!("error: ({})", error.display_with_causes()), "error: (context: inner)");
    /// ```
    fn display_with_causes(&self) -> ErrorChainFormatter<&Self> {
        ErrorChainFormatter(self)
    }

    /// Converts `self` to a string `String`, along with the chain of _source_ errors or
    /// causes, if there are any.
    ///
    /// # Examples
    ///
    /// Basic usage:
    ///
    /// ```
    /// use anyhow::anyhow;
    /// use mz_ore::error::ErrorExt;
    ///
    /// let error = anyhow!("inner");
    /// let error = error.context("context");
    /// assert_eq!(error.to_string_with_causes(), "context: inner");
    /// ```
    fn to_string_with_causes(&self) -> String {
        format!("{}", self.display_with_causes())
    }
}

impl<E: Error + ?Sized> ErrorExt for E {}

/// Formats an error with its full chain of causes.
pub struct ErrorChainFormatter<E>(E);

impl<E: Error> Display for ErrorChainFormatter<E> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Display::fmt(&self.0, f)?;

        let mut maybe_cause = self.0.source();

        while let Some(cause) = maybe_cause {
            write!(f, ": {}", cause)?;
            maybe_cause = cause.source();
        }

        Ok(())
    }
}

impl<E: Error> Debug for ErrorChainFormatter<E> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        Debug::fmt(&self.0, f)
    }
}

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use anyhow::anyhow;

    use super::*;

    #[crate::test]
    fn basic_usage() {
        let error = anyhow!("root");
        let error = error.context("context");
        assert_eq!(error.to_string_with_causes(), "context: root");
    }

    #[crate::test]
    fn basic_usage_with_arc() {
        // The reason for this signature is that our Plan errors have a `cause` field like this:
        // ```
        // cause: Arc<dyn Error + Send + Sync>
        // ```
        fn verify(dyn_error: Arc<dyn Error + Send + Sync>) {
            assert_eq!(
                dyn_error.to_string_with_causes(),
                "test error: context: root"
            );
        }

        let error = anyhow!("root");
        let error = error.context("context");
        let error = TestError { inner: error };
        let arc_error = Arc::new(error);
        verify(arc_error);
    }

    #[derive(Debug)]
    struct TestError {
        inner: anyhow::Error,
    }

    impl Display for TestError {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            // We don't print our causes.
            write!(f, "test error")?;
            Ok(())
        }
    }

    impl std::error::Error for TestError {
        fn source(&self) -> Option<&(dyn Error + 'static)> {
            Some(self.inner.as_ref())
        }
    }
}