embedded_error_chain/
error.rs

1use crate::{
2    error_category::{self, ErrorCodeFormatter},
3    error_data::ErrorDataChainIter,
4    marker, DynError, ErrorCategory, ErrorCategoryHandle, ErrorCode, ErrorData, ERROR_CHAIN_LEN,
5};
6use core::marker::PhantomData;
7use core::{
8    fmt::{self, Debug, Formatter},
9    iter::FusedIterator,
10};
11
12/// A typed error with an optional error chain of up to four source errors that represent
13/// the cause of this error.
14///
15/// The error chain is a singly linked list of most recent to oldest source error with a
16/// maximum length of 4. When chaining two errors with [`chain()`](ChainError::chain()) or
17/// [`chain_err()`](ResultChainError::chain_err()) the error code of the current error is
18/// prepended to the front of the linked list. If the linked list is already at its
19/// maximum length before chaining, and the feature `panic-on-overflow` is enabled, the
20/// chaining operation will panic, otherwise the oldest error will be lost. After the
21/// current error code has been prepended, the new error code will be set as the current
22/// and the chain operation will return a new [`Error`] typed with the [`ErrorCategory`]
23/// of the new error code.
24///
25/// Chaining an [`Error`] with a new error code is only possible if the [`ErrorCategory`]
26/// of the new error code links to the [`ErrorCategory`] of the current error code. This
27/// is done using the `links` argument in the `error_category` attribute, if
28/// [`ErrorCategory`] is implemented using the derive macro.
29///
30/// ```
31/// # use embedded_error_chain::prelude::*;
32/// #[derive(Clone, Copy, ErrorCategory)]
33/// #[repr(u8)]
34/// enum CurrentError {
35///     Err
36/// }
37///
38/// #[derive(Clone, Copy, ErrorCategory)]
39/// #[error_category(links(CurrentError))]
40/// //               ~~~~~~~~~~~~~~~~~~~
41/// // This allows `CurrentError` to be chained with `NewError`
42/// #[repr(u8)]
43/// enum NewError {
44///     Err
45/// }
46///
47/// // Chain example
48/// fn do_chain() -> Error<NewError> {
49///     (CurrentError::Err).chain(NewError::Err)
50/// }
51/// # do_chain();
52/// ```
53///
54/// This does not compile:
55/// ```compile_fail
56/// # use embedded_error_chain::prelude::*;
57/// #[derive(Clone, Copy, ErrorCategory)]
58/// #[repr(u8)]
59/// enum CurrentError {
60///     Err
61/// }
62///
63/// #[derive(Clone, Copy, ErrorCategory)]
64/// #[repr(u8)]
65/// enum NewError {
66///     Err
67/// }
68///
69/// // Chain example
70/// fn do_chain() -> Error<NewError> {
71///     (CurrentError::Err).chain(NewError::Err)
72/// }
73/// # do_chain();
74/// ```
75///
76/// Unlike [`DynError`](crate::DynError) which does not have a type parameter, [`Error`]'s
77/// type parameter specifies the [`ErrorCategory`] of the most recent error (also called
78/// current error). This allows the size of this struct to be reduced and so the struct is
79/// guaranteed to only be one [`u32`] or 4 bytes in size (the same size as [`ErrorData`]),
80/// whereas [`DynError`](crate::DynError) contains an additional pointer ([`usize`]).
81///
82/// Additionally because the [error category](`ErrorCategory`) of the first error is known at
83/// compile time, this allows for the [`chain()`](ChainError::chain()) and
84/// [`chain_err()`](ResultChainError::chain_err()) operations to be error checked at
85/// compile time and for them to have a constant execution time `O(1)`.
86///
87/// But a consequence of this type parameter is, that when returning an [`Error`] from a
88/// function that has to forward multiple errors of *different* [error
89/// categories](ErrorCategory), you must always [`chain()`](ChainError::chain()) or
90/// [`chain_err()`](ResultChainError::chain_err()) them. So instead of an inner error
91/// being directly forwarded to the caller, you must have a single error of indirection in
92/// between.
93///
94/// ```
95/// # use embedded_error_chain::prelude::*;
96/// #[derive(Clone, Copy, ErrorCategory)]
97/// #[repr(u8)]
98/// enum InnerError {
99///     SomeError
100/// }
101///
102/// #[derive(Clone, Copy, ErrorCategory)]
103/// #[error_category(links(InnerError))]
104/// #[repr(u8)]
105/// enum OuterError {
106///     Inner
107/// }
108///
109/// # fn causes_inner_error() -> Result<(), InnerError> { Err(InnerError::SomeError) }
110/// #
111/// fn do_something_that_may_error() -> Result<(), Error<OuterError>> {
112///     causes_inner_error().chain_err(OuterError::Inner)?;
113///     // other stuff that causes `OuterError`...
114/// #   Ok(())
115/// }
116/// # do_something_that_may_error();
117/// ```
118///
119/// If you want to directly forward a single or multiple source errors with different
120/// unrelated [error categories](ErrorCategory) and you don't need the advantages outlined
121/// above use [`DynError`] instead.
122#[repr(transparent)]
123pub struct Error<C>(ErrorData, PhantomData<C>);
124
125impl<C> Error<C> {
126    /// Create a new [`Error`] with an empty chain from the supplied raw `error_code`.
127    ///
128    /// This function is memory-safe and will never panic, but if `error_code` is not part the
129    /// [`ErrorCategory`] `C` the behavior of all method calls on the returned [`Error`]
130    /// is undefined.
131    #[inline(always)]
132    pub const fn new_raw(error_code: ErrorCode) -> Error<C> {
133        Error(ErrorData::new(error_code), PhantomData)
134    }
135
136    /// Crate a new [`Error`] from raw [`ErrorData`].
137    ///
138    /// This function is memory-safe and will never panic, but if `error_data.code()` is
139    /// not part the [`ErrorCategory`] `C` or the contained error chain is invalid, the
140    /// behavior of all method calls on the returned [`Error`] is undefined.
141    pub const fn from_raw(error_data: ErrorData) -> Error<C> {
142        Error(error_data, PhantomData)
143    }
144}
145
146impl<C> Error<C> {
147    /// Get the capacity of the error chain.
148    ///
149    /// Always returns [`ERROR_CHAIN_LEN`].
150    pub const fn chain_capacity(&self) -> usize {
151        ERROR_CHAIN_LEN
152    }
153}
154
155impl<C: ErrorCategory> Error<C> {
156    /// Create a new [`Error`] with an empty chain from the supplied `error_code`.
157    #[inline(always)]
158    pub fn new(error_code: C) -> Error<C> {
159        Error(ErrorData::new(error_code.into()), PhantomData)
160    }
161
162    /// Get the error code of the latest error.
163    #[inline]
164    pub fn code(&self) -> C {
165        self.0.code().into()
166    }
167
168    /// Get the length of the error chain.
169    pub fn chain_len(&self) -> usize {
170        self.0.chain_len()
171    }
172
173    /// Query if this error was caused by `error_code` which belongs to the error category
174    /// `T`.
175    pub fn caused_by<T: ErrorCategory>(&self, error_code: T) -> bool {
176        let error_code: ErrorCode = error_code.into();
177        let category_handle = ErrorCategoryHandle::new::<T>();
178
179        self.iter()
180            .any(|(ec, handle)| handle == category_handle && ec == error_code)
181    }
182
183    /// Query the error code contained in this error that belongs to the [`ErrorCategory`]
184    /// `T`. Return `None` if this error was not caused by the specified error category.
185    pub fn code_of_category<T: ErrorCategory>(&self) -> Option<T> {
186        let category_handle = ErrorCategoryHandle::new::<T>();
187        self.iter().find_map(|(ec, handle)| {
188            if handle == category_handle {
189                Some(ec.into())
190            } else {
191                None
192            }
193        })
194    }
195
196    /// Create an iterator that iterates over all error codes in this error.
197    pub fn iter(&self) -> ErrorIter {
198        ErrorIter {
199            formatter_func: Some(error_category::format_chained::<C>),
200            curr_error_code: self.0.code(),
201            next_formatter_index: self.0.first_formatter_index(),
202            chain_iter: self.0.iter_chain(),
203        }
204    }
205}
206
207/// An iterator over all error codes in this [`Error`].
208///
209/// Returns a tuple with the following items:
210/// - `0`: The [`ErrorCode`] of this error.
211/// - `1`: A [`ErrorCategoryHandle`] to the [`ErrorCategory`](super::ErrorCategory) of
212///   this error.
213pub struct ErrorIter {
214    pub(crate) formatter_func: Option<ErrorCodeFormatter>,
215    pub(crate) curr_error_code: ErrorCode,
216    pub(crate) next_formatter_index: Option<u8>,
217    pub(crate) chain_iter: ErrorDataChainIter,
218}
219
220impl Iterator for ErrorIter {
221    type Item = (ErrorCode, ErrorCategoryHandle);
222
223    fn next(&mut self) -> Option<Self::Item> {
224        if let Some(formatter_func) = self.formatter_func {
225            let (err_cat_handle, next_formatter_res) =
226                formatter_func(0, self.next_formatter_index.take(), None);
227            let error_code = self.curr_error_code;
228
229            if let (Some((next_error_code, next_next_formatter_index)), Ok(Some(next_formatter))) =
230                (self.chain_iter.next(), next_formatter_res)
231            {
232                self.curr_error_code = next_error_code;
233                self.next_formatter_index = next_next_formatter_index;
234                self.formatter_func = Some(next_formatter.into());
235            } else {
236                self.formatter_func = None;
237            }
238
239            Some((error_code, err_cat_handle))
240        } else {
241            None
242        }
243    }
244}
245impl FusedIterator for ErrorIter {}
246
247impl<C: ErrorCategory> Debug for Error<C> {
248    /// Debug format this error and its chain.
249    ///
250    /// Delegates to [`DynError::fmt()`].
251    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
252        DynError::from(*self).fmt(f)
253    }
254}
255
256/// A trait that allows chaining of [`Error`] and [`DynError`](crate::DynError) values and
257/// any value of a type that implements [`ErrorCategory`].
258pub trait ChainError<O: ErrorCategory, Tag> {
259    /// Chain this error with the supplied `error_code`.
260    ///
261    /// ### Panics
262    /// If the [error category](ErrorCategory) `O` is not linked with the [`ErrorCategory`]
263    /// of the most recent error code, this function will panic.
264    fn chain(self, error_code: O) -> Error<O>;
265}
266
267/// A trait that allows chaining if a [`Result`] contains an [`Error`] value.
268pub trait ResultChainError<T, O: ErrorCategory, Tag> {
269    /// If the results contains an [`Err`] value, chain it with the supplied `error_code`
270    /// and return [`Err`] with the result, otherwise forward the [`Ok`] value.
271    ///
272    /// ### Panics
273    /// If this [`Result`] is an [`Err`] value and the [error category](ErrorCategory) `O`
274    /// is not linked with the [`ErrorCategory`] of the most recent error code in the
275    /// error, this function will panic.
276    fn chain_err(self, error_code: O) -> Result<T, Error<O>>;
277}
278
279macro_rules! impl_chain_error {
280    ($([$t:ident, $idx:literal]),*) => {
281        $(
282            impl<C: ErrorCategory> ChainError<C, (marker::$t, marker::Error_t)> for Error<C::$t> {
283                #[inline(always)]
284                fn chain(self, error_code: C) -> Error<C> {
285                    let mut data: ErrorData = self.0;
286                    ErrorData::chain(&mut data, error_code.into(), $idx);
287                    Error(data, PhantomData)
288                }
289            }
290
291            impl<C: ErrorCategory> ChainError<C, (marker::$t, marker::Concrete_t)> for C::$t {
292                #[inline(always)]
293                fn chain(self, error_code: C) -> Error<C> {
294                    Error::new(self).chain(error_code)
295                }
296            }
297        )+
298    };
299}
300
301impl_chain_error!([L0, 0], [L1, 1], [L2, 2], [L3, 3], [L4, 4], [L5, 5]);
302
303impl<OK, ERR, O, TAG> ResultChainError<OK, O, TAG> for Result<OK, ERR>
304where
305    O: ErrorCategory,
306    ERR: ChainError<O, TAG>,
307{
308    #[inline]
309    fn chain_err(self, error_code: O) -> Result<OK, Error<O>> {
310        match self {
311            Err(err) => Err(err.chain(error_code)),
312            Ok(val) => Ok(val),
313        }
314    }
315}
316
317impl<C: ErrorCategory> PartialEq for Error<C> {
318    fn eq(&self, other: &Error<C>) -> bool {
319        self.0 == other.0
320    }
321}
322impl<C: ErrorCategory> Eq for Error<C> {}
323
324impl<C: ErrorCategory> Clone for Error<C> {
325    #[inline(always)]
326    fn clone(&self) -> Self {
327        Error(self.0, PhantomData)
328    }
329
330    #[inline(always)]
331    fn clone_from(&mut self, source: &Self) {
332        self.0 = source.0;
333    }
334}
335
336impl<C: ErrorCategory> Copy for Error<C> {}
337
338impl<C: ErrorCategory> From<C> for Error<C> {
339    #[inline(always)]
340    fn from(error_code: C) -> Self {
341        Error::new(error_code)
342    }
343}
344
345impl<C: ErrorCategory> From<Error<C>> for ErrorData {
346    #[inline(always)]
347    fn from(error: Error<C>) -> Self {
348        error.0
349    }
350}