1#![deny(warnings)]
10#![deny(missing_docs)]
11
12pub type GenericError = anyhow::Error;
20
21#[macro_export]
30macro_rules! generic_error {
31 ($msg:literal $(,)?) => { $crate::_anyhow!($msg) };
35 ($err:expr $(,)?) => { $crate::_anyhow!($err) };
36 ($fmt:expr, $($arg:tt)*) => { $crate::_anyhow!($fmt, $($arg)*) };
37}
38
39use std::fmt::Display;
40
41#[doc(hidden)]
42pub use anyhow::anyhow as _anyhow;
43
44mod private {
45 pub trait Sealed {}
46
47 impl<T, E> Sealed for Result<T, E> {}
48}
49
50pub trait ErrorContext<T, E>: private::Sealed {
55 fn error_context<C>(self, context: C) -> Result<T, GenericError>
57 where
58 C: Display + Send + Sync + 'static;
59
60 fn with_error_context<C, F>(self, f: F) -> Result<T, GenericError>
62 where
63 C: Display + Send + Sync + 'static,
64 F: FnOnce() -> C;
65}
66
67impl<T, E> ErrorContext<T, E> for Result<T, E>
68where
69 Result<T, E>: anyhow::Context<T, E>,
70{
71 fn error_context<C>(self, context: C) -> Result<T, GenericError>
72 where
73 C: Display + Send + Sync + 'static,
74 {
75 <Self as anyhow::Context<T, E>>::context(self, context)
76 }
77
78 fn with_error_context<C, F>(self, context: F) -> Result<T, GenericError>
79 where
80 C: Display + Send + Sync + 'static,
81 F: FnOnce() -> C,
82 {
83 <Self as anyhow::Context<T, E>>::with_context(self, context)
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use std::cell::Cell;
90 use std::fmt;
91
92 use super::ErrorContext as _;
93
94 #[derive(Debug)]
96 struct LeafError;
97
98 impl fmt::Display for LeafError {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 write!(f, "leaf failure")
101 }
102 }
103
104 impl std::error::Error for LeafError {}
105
106 #[derive(Debug)]
108 struct WrappingError {
109 source: LeafError,
110 }
111
112 impl fmt::Display for WrappingError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "wrapping failure")
115 }
116 }
117
118 impl std::error::Error for WrappingError {
119 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
120 Some(&self.source)
121 }
122 }
123
124 #[test]
125 fn generic_error_from_string_literal_uses_message_as_display() {
126 let err = generic_error!("boom");
127 assert_eq!(err.to_string(), "boom");
128 }
129
130 #[test]
131 fn generic_error_from_format_string_interpolates_arguments() {
132 let err = generic_error!("value {} out of range {}", 42, "here");
133 assert_eq!(err.to_string(), "value 42 out of range here");
134 }
135
136 #[test]
137 fn generic_error_from_error_value_forwards_source_chain() {
138 let err = generic_error!(WrappingError { source: LeafError });
142 assert_eq!(err.to_string(), "wrapping failure");
143 assert_eq!(err.root_cause().to_string(), "leaf failure");
144 }
145
146 #[test]
147 fn error_context_passes_through_ok_unchanged() {
148 let result: Result<i32, std::io::Error> = Ok(7);
149 let contextualized = result.error_context("this context should never appear");
150 assert_eq!(contextualized.unwrap(), 7);
151 }
152
153 #[test]
154 fn error_context_wraps_err_with_context_and_preserves_source() {
155 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
156 let result: Result<(), std::io::Error> = Err(io_err);
157
158 let err = result.error_context("while loading config").unwrap_err();
159 assert_eq!(err.to_string(), "while loading config");
160 assert_eq!(err.root_cause().to_string(), "permission denied");
161 }
162
163 #[test]
164 fn with_error_context_is_not_evaluated_on_ok() {
165 let called = Cell::new(false);
166 let result: Result<i32, std::io::Error> = Ok(1);
167
168 let contextualized = result.with_error_context(|| {
169 called.set(true);
170 "lazy context"
171 });
172
173 assert_eq!(contextualized.unwrap(), 1);
174 assert!(!called.get(), "context closure must not run on the Ok path");
175 }
176
177 #[test]
178 fn with_error_context_is_evaluated_and_applied_on_err() {
179 let called = Cell::new(false);
180 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
181 let result: Result<(), std::io::Error> = Err(io_err);
182
183 let err = result
184 .with_error_context(|| {
185 called.set(true);
186 "lazy context"
187 })
188 .unwrap_err();
189
190 assert!(called.get(), "context closure must run on the Err path");
191 assert_eq!(err.to_string(), "lazy context");
192 assert_eq!(err.root_cause().to_string(), "missing");
193 }
194}