Skip to main content

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
169impl From<Error> for anyhow::Error {
170    #[inline]
171    fn from(err: Error) -> Self {
172        Self::msg(err.to_string())
173    }
174}
175
176/// Emits a `tracing::Event` at the warn level and returns early with an [`Error`].
177#[macro_export]
178macro_rules! bail {
179    ($message:literal $(,)?) => {{
180        tracing::warn!($message);
181        return Err(Error::new($message));
182    }};
183    ($err:expr $(,)?) => {{
184        tracing::warn!($err);
185        return Err(Error::from($err));
186    }};
187    ($fmt:expr, $($arg:tt)+) => {{
188        let message = format!($fmt, $($arg)+);
189        tracing::warn!(message);
190        return Err(Error::new(message));
191    }};
192}
193
194/// Emits a `tracing::Event` at the warn level and constructs an [`Error`].
195#[macro_export]
196macro_rules! warn {
197    ($message:literal $(,)?) => {{
198        tracing::warn!($message);
199        Error::new($message)
200    }};
201    ($err:expr $(,)?) => {{
202        tracing::warn!($err);
203        Error::from($err)
204    }};
205    ($fmt:expr, $($arg:tt)+) => {{
206        let message = format!($fmt, $($arg)+);
207        tracing::warn!(message);
208        Error::new(message)
209    }};
210}