n0_error/
any.rs

1use std::{
2    convert::Infallible,
3    fmt::{self, Formatter},
4    ops::Deref,
5};
6
7use crate::{ErrorRef, FromString, Meta, SourceFormat, StackError, StackErrorExt};
8
9/// Type-erased error that can wrap a [`StackError`] or any [`std::error::Error`].
10///
11/// [`StackError`]s have a blanket impl to convert into [`AnyError`], while preserving
12/// their call-site location info.
13///
14/// Errors that implement [`std::error::Error`] but not [`StackError`] can't convert to
15/// [`AnyError`] automatically. Use either [`AnyError::from_std`] or [`crate::StdResultExt::anyerr`]
16/// to convert std errors into [`AnyError`].
17///
18/// This is necessary unfortunately because if we had a blanket conversion from std errors to `AnyError`,
19/// this blanket conversion would also apply to [`StackError`]s, thus losing their call-site location
20/// info in the conversion.
21pub struct AnyError(Inner);
22
23enum Inner {
24    Stack(Box<dyn StackError>),
25    Std(Box<dyn std::error::Error + Send + Sync>, Meta),
26}
27
28impl AnyError {
29    /// Creates an [`AnyError`] from a std error.
30    ///
31    /// This captures call-site metadata.
32    #[track_caller]
33    pub fn from_std(err: impl std::error::Error + Send + Sync + 'static) -> Self {
34        Self::from_std_box(Box::new(err))
35    }
36
37    /// Creates an [`AnyError`] from a [`anyhow::Error`].
38    #[track_caller]
39    #[cfg(feature = "anyhow")]
40    pub fn from_anyhow(err: anyhow::Error) -> Self {
41        Self::from_std_box(err.into_boxed_dyn_error())
42    }
43
44    /// Creates an [`AnyError`] from a bosed std error.
45    ///
46    /// This captures call-site metadata.
47    #[track_caller]
48    pub fn from_std_box(err: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
49        Self(Inner::Std(err, Meta::default()))
50    }
51
52    /// Creates an [`AnyError`] from any `Display` value by formatting it into a message.
53    #[track_caller]
54    pub fn from_display(s: impl fmt::Display) -> Self {
55        Self::from_string(s.to_string())
56    }
57
58    /// Creates an [`AnyError`] from a message string.
59    #[track_caller]
60    pub fn from_string(message: String) -> Self {
61        FromString::WithoutSource {
62            message,
63            meta: Meta::default(),
64        }
65        .into_any()
66    }
67
68    /// Creates an [`AnyError`] from a [`StackError`].
69    ///
70    /// This preserves the error's call-site metadata.
71    ///
72    /// Equivalent to `AnyError::from(err)`.
73    #[track_caller]
74    pub fn from_stack(err: impl StackError + 'static) -> Self {
75        Self::from_stack_box(Box::new(err))
76    }
77
78    /// Creates an [`AnyError`] from a boxed [`StackError`].
79    #[track_caller]
80    pub fn from_stack_box(err: Box<dyn StackError>) -> Self {
81        Self(Inner::Stack(err))
82    }
83
84    /// Adds additional context on top of this error.
85    #[track_caller]
86    pub fn context(self, context: impl fmt::Display) -> AnyError {
87        FromString::WithSource {
88            message: context.to_string(),
89            source: self,
90            meta: Meta::default(),
91        }
92        .into_any()
93    }
94
95    /// Converts into boxed std error.
96    pub fn into_boxed_dyn_error(self) -> Box<dyn std::error::Error + Send + Sync + 'static> {
97        Box::new(self)
98    }
99
100    /// Downcast this error object by reference.
101    pub fn downcast_ref<T: std::error::Error + 'static>(&self) -> Option<&T> {
102        match &self.0 {
103            Inner::Stack(err) => err.as_std().downcast_ref(),
104            Inner::Std(err, _) => err.downcast_ref(),
105        }
106    }
107
108    /// Downcast this error object by reference.
109    pub fn downcast<T: std::error::Error + 'static>(self) -> Option<T> {
110        match self.0 {
111            Inner::Stack(err) => err.into_std().downcast().ok().map(|b| *b),
112            Inner::Std(err, _) => err.downcast().ok().map(|b| *b),
113        }
114    }
115}
116
117impl fmt::Display for AnyError {
118    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119        let sources = f.alternate().then_some(SourceFormat::OneLine);
120        write!(f, "{}", self.report().sources(sources))
121    }
122}
123
124impl fmt::Debug for AnyError {
125    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
126        if f.alternate() {
127            match &self.0 {
128                Inner::Stack(error) => {
129                    write!(f, "Stack({error:#?})")
130                }
131                Inner::Std(error, meta) => {
132                    write!(f, "Std({error:#?}, {meta:?})")
133                }
134            }
135        } else {
136            write!(f, "{}", self.report().full())
137        }
138    }
139}
140
141impl StackError for AnyError {
142    fn as_dyn(&self) -> &dyn StackError {
143        self
144    }
145    fn into_std(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync> {
146        self
147    }
148
149    fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
150        match &self.0 {
151            Inner::Std(err, _) => err.as_ref(),
152            Inner::Stack(err) => err.as_std(),
153        }
154    }
155
156    fn meta(&self) -> Option<&Meta> {
157        match &self.0 {
158            Inner::Std(_, meta) => Some(meta),
159            Inner::Stack(err) => err.meta(),
160        }
161    }
162
163    fn source(&self) -> Option<ErrorRef<'_>> {
164        self.as_ref().source()
165    }
166
167    fn is_transparent(&self) -> bool {
168        self.as_ref().is_transparent()
169    }
170
171    fn as_ref<'a>(&'a self) -> ErrorRef<'a> {
172        match &self.0 {
173            Inner::Stack(error) => ErrorRef::Stack(error.deref()),
174            Inner::Std(error, meta) => ErrorRef::std_with_meta(error.as_ref(), meta),
175        }
176    }
177
178    fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        match &self.0 {
180            Inner::Stack(error) => error.fmt_message(f),
181            Inner::Std(error, _) => fmt::Display::fmt(error, f),
182        }
183    }
184}
185
186impl std::error::Error for AnyError {
187    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
188        self.as_std().source()
189    }
190}
191
192impl From<String> for AnyError {
193    #[track_caller]
194    fn from(value: String) -> Self {
195        Self::from_display(value)
196    }
197}
198
199impl From<&str> for AnyError {
200    #[track_caller]
201    fn from(value: &str) -> Self {
202        Self::from_display(value)
203    }
204}
205
206impl From<Box<dyn std::error::Error + Send + Sync>> for AnyError {
207    #[track_caller]
208    fn from(value: Box<dyn std::error::Error + Send + Sync>) -> Self {
209        Self::from_std_box(value)
210    }
211}
212
213#[cfg(feature = "anyhow")]
214impl From<anyhow::Error> for AnyError {
215    #[track_caller]
216    fn from(value: anyhow::Error) -> Self {
217        Self::from_anyhow(value)
218    }
219}
220
221impl From<std::io::Error> for AnyError {
222    #[track_caller]
223    fn from(value: std::io::Error) -> Self {
224        Self::from_std(value)
225    }
226}
227
228impl std::str::FromStr for AnyError {
229    type Err = Infallible;
230
231    #[track_caller]
232    fn from_str(s: &str) -> Result<Self, Self::Err> {
233        Ok(Self::from_display(s))
234    }
235}