zino_core/error/
mod.rs

1//! Type-erased errors with tracing functionalities.
2use crate::SharedString;
3use std::{any::Any, error, fmt};
4
5mod source;
6
7use source::Source;
8
9/// An error type backed by an allocation-optimized string.
10#[derive(Debug)]
11pub struct Error {
12    /// Error message.
13    message: SharedString,
14    /// Error source.
15    source: Option<Box<Error>>,
16    /// Error context.
17    context: Option<Box<dyn Any + Send>>,
18}
19
20impl Clone for Error {
21    #[inline]
22    fn clone(&self) -> Self {
23        Self {
24            message: self.message.clone(),
25            source: self.source.clone(),
26            context: None,
27        }
28    }
29}
30
31impl PartialEq for Error {
32    #[inline]
33    fn eq(&self, other: &Self) -> bool {
34        self.message == other.message && self.source == other.source
35    }
36}
37
38impl Error {
39    /// Creates a new instance with the supplied message.
40    #[inline]
41    pub fn new(message: impl Into<SharedString>) -> Self {
42        Self {
43            message: message.into(),
44            source: None,
45            context: None,
46        }
47    }
48
49    /// Creates a new instance with the supplied message and the error source.
50    #[inline]
51    pub fn with_source(message: impl Into<SharedString>, source: impl Into<Error>) -> Self {
52        Self {
53            message: message.into(),
54            source: Some(Box::new(source.into())),
55            context: None,
56        }
57    }
58
59    /// Creates a new instance from [`std::error::Error`] by discarding the context.
60    #[inline]
61    pub fn from_error(err: impl error::Error) -> Self {
62        Self {
63            message: err.to_string().into(),
64            source: err.source().map(|err| Box::new(Self::new(err.to_string()))),
65            context: None,
66        }
67    }
68
69    /// Wraps the error value with additional contextual message.
70    #[inline]
71    pub fn wrap(self, message: impl Into<SharedString>) -> Self {
72        Self {
73            message: message.into(),
74            source: Some(Box::new(self)),
75            context: None,
76        }
77    }
78
79    /// Sets a context for the error.
80    #[inline]
81    pub fn set_context<T: Send + 'static>(&mut self, context: T) {
82        self.context = Some(Box::new(context));
83    }
84
85    /// Gets a reference to the context of the error.
86    #[inline]
87    pub fn get_context<T: Send + 'static>(&self) -> Option<&T> {
88        self.context
89            .as_ref()
90            .and_then(|ctx| ctx.downcast_ref::<T>())
91    }
92
93    /// Gets a mutable reference to the context of the error.
94    #[inline]
95    pub fn get_context_mut<T: Send + 'static>(&mut self) -> Option<&mut T> {
96        self.context
97            .as_mut()
98            .and_then(|ctx| ctx.downcast_mut::<T>())
99    }
100
101    /// Takes the context out of the error.
102    #[inline]
103    pub fn take_context<T: Send + 'static>(&mut self) -> Option<Box<T>> {
104        self.context.take().and_then(|ctx| ctx.downcast::<T>().ok())
105    }
106
107    /// Returns `true` if the error has a context with type `T`.
108    #[inline]
109    pub fn has_context<T: Send + 'static>(&self) -> bool {
110        self.context.as_ref().is_some_and(|ctx| ctx.is::<T>())
111    }
112
113    /// Returns the error message.
114    #[inline]
115    pub fn message(&self) -> &str {
116        self.message.as_ref()
117    }
118
119    /// Returns the error source.
120    #[inline]
121    pub fn source(&self) -> Option<&Error> {
122        self.source.as_deref()
123    }
124
125    /// Returns an iterator of the source errors contained by `self`.
126    #[inline]
127    pub fn sources(&self) -> impl Iterator<Item = &Error> {
128        Source::new(self)
129    }
130
131    /// Returns the lowest level source of `self`.
132    ///
133    /// The root source is the last error in the iterator produced by [`sources()`](Error::sources).
134    #[inline]
135    pub fn root_source(&self) -> Option<&Error> {
136        self.sources().last()
137    }
138}
139
140impl<E: error::Error + Send + 'static> From<E> for Error {
141    #[inline]
142    fn from(err: E) -> Self {
143        Self {
144            message: err.to_string().into(),
145            source: err.source().map(|err| Box::new(Self::new(err.to_string()))),
146            context: Some(Box::new(err)),
147        }
148    }
149}
150
151impl fmt::Display for Error {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        let message = self.message();
154        if let Some(source) = &self.source {
155            let source = source.message();
156            let root_source = self.root_source().map(|err| err.message());
157            if root_source != Some(source) {
158                tracing::error!(root_source, source, message);
159            } else {
160                tracing::error!(root_source, message);
161            }
162        } else {
163            tracing::error!(message);
164        }
165        write!(f, "{message}")
166    }
167}
168
169/// Emits a `tracing::Event` at the warn level and returns early with an [`Error`].
170#[macro_export]
171macro_rules! bail {
172    ($message:literal $(,)?) => {{
173        tracing::warn!($message);
174        return Err(Error::new($message));
175    }};
176    ($err:expr $(,)?) => {{
177        tracing::warn!($err);
178        return Err(Error::from($err));
179    }};
180    ($fmt:expr, $($arg:tt)+) => {{
181        let message = format!($fmt, $($arg)+);
182        tracing::warn!(message);
183        return Err(Error::new(message));
184    }};
185}
186
187/// Emits a `tracing::Event` at the warn level and constructs an [`Error`].
188#[macro_export]
189macro_rules! warn {
190    ($message:literal $(,)?) => {{
191        tracing::warn!($message);
192        Error::new($message)
193    }};
194    ($err:expr $(,)?) => {{
195        tracing::warn!($err);
196        Error::from($err)
197    }};
198    ($fmt:expr, $($arg:tt)+) => {{
199        let message = format!($fmt, $($arg)+);
200        tracing::warn!(message);
201        Error::new(message)
202    }};
203}