mz_ore/
result.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//! Result utilities.
17
18use std::convert::Infallible;
19
20use crate::error::ErrorExt;
21
22/// Extension methods for [`std::result::Result`].
23pub trait ResultExt<T, E> {
24    /// Applies [`Into::into`] to a contained [`Err`] value, leaving an [`Ok`]
25    /// value untouched.
26    fn err_into<E2>(self) -> Result<T, E2>
27    where
28        E: Into<E2>;
29
30    /// Formats an [`Err`] value as a detailed error message, preserving any context information.
31    ///
32    /// This is equivalent to `format!("{}", err.display_with_causes())`, except that it's easier to
33    /// type.
34    fn err_to_string_with_causes(&self) -> Option<String>
35    where
36        E: std::error::Error;
37
38    /// Maps a `Result<T, E>` to `Result<T, String>` by converting the [`Err`] result into a string,
39    /// along with the chain of source errors, if any.
40    fn map_err_to_string_with_causes(self) -> Result<T, String>
41    where
42        E: std::error::Error;
43
44    /// Safely unwraps a `Result<T, Infallible>`, where [`Infallible`] is a type that represents when
45    /// an error cannot occur.
46    fn infallible_unwrap(self) -> T
47    where
48        E: Into<Infallible>;
49}
50
51impl<T, E> ResultExt<T, E> for Result<T, E> {
52    fn err_into<E2>(self) -> Result<T, E2>
53    where
54        E: Into<E2>,
55    {
56        self.map_err(|e| e.into())
57    }
58
59    fn err_to_string_with_causes(&self) -> Option<String>
60    where
61        E: std::error::Error,
62    {
63        self.as_ref().err().map(ErrorExt::to_string_with_causes)
64    }
65
66    fn map_err_to_string_with_causes(self) -> Result<T, String>
67    where
68        E: std::error::Error,
69    {
70        self.map_err(|e| ErrorExt::to_string_with_causes(&e))
71    }
72
73    fn infallible_unwrap(self) -> T
74    where
75        E: Into<Infallible>,
76    {
77        #[allow(unreachable_code)]
78        self.unwrap_or_else(|e| {
79            let _infallible = e.into();
80
81            // This code will forever be unreachable because Infallible is an enum
82            // with no variants, so it's impossible to construct. If it ever does
83            // become possible to construct this will become a compile time error
84            // since there will be a variant we're not matching on.
85            match _infallible {}
86        })
87    }
88}
89
90#[cfg(test)]
91mod test {
92    use std::fmt::Display;
93
94    use anyhow::anyhow;
95
96    use super::*;
97
98    #[crate::test]
99    fn prints_error_chain() {
100        let error = anyhow!("root");
101        let error = error.context("context");
102        let error = TestError { inner: error };
103        let res: Result<(), _> = Err(error);
104
105        assert_eq!(
106            res.err_to_string_with_causes(),
107            Some("test error: context: root".to_string())
108        );
109
110        let error = anyhow!("root");
111        let error = error.context("context");
112        let error = TestError { inner: error };
113        let res: Result<(), _> = Err(error);
114
115        assert_eq!(
116            res.map_err_to_string_with_causes(),
117            Err("test error: context: root".to_string())
118        );
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 std::fmt::Formatter<'_>) -> std::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 std::error::Error + 'static)> {
136            Some(self.inner.as_ref())
137        }
138    }
139}