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 match self {
78 Ok(t) => t,
79 Err(e) => {
80 let _infallible = e.into();
81
82 // This code will forever be unreachable because Infallible is an enum
83 // with no variants, so it's impossible to construct. If it ever does
84 // become possible to construct this will become a compile time error
85 // since there will be a variant we're not matching on.
86 #[allow(unreachable_code)]
87 match _infallible {}
88 }
89 }
90 }
91}
92
93#[cfg(test)]
94mod test {
95 use std::fmt::Display;
96
97 use anyhow::anyhow;
98
99 use super::*;
100
101 #[crate::test]
102 fn prints_error_chain() {
103 let error = anyhow!("root");
104 let error = error.context("context");
105 let error = TestError { inner: error };
106 let res: Result<(), _> = Err(error);
107
108 assert_eq!(
109 res.err_to_string_with_causes(),
110 Some("test error: context: root".to_string())
111 );
112
113 let error = anyhow!("root");
114 let error = error.context("context");
115 let error = TestError { inner: error };
116 let res: Result<(), _> = Err(error);
117
118 assert_eq!(
119 res.map_err_to_string_with_causes(),
120 Err("test error: context: root".to_string())
121 );
122 }
123
124 #[derive(Debug)]
125 struct TestError {
126 inner: anyhow::Error,
127 }
128
129 impl Display for TestError {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 // We don't print our causes.
132 write!(f, "test error")?;
133 Ok(())
134 }
135 }
136
137 impl std::error::Error for TestError {
138 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
139 Some(self.inner.as_ref())
140 }
141 }
142}