embedded_error_chain/
dyn_error.rs

1use crate::{
2    format_chained, ChainError, Error, ErrorCategory, ErrorCategoryHandle, ErrorCode,
3    ErrorCodeFormatter, ErrorData, ErrorIter, ERROR_CHAIN_LEN,
4};
5use core::{fmt, ptr};
6
7/// Untyped counterpart to [`Error`].
8///
9/// This struct functions mostly identical to [`Error`] though its contained error code
10/// can be of any [`ErrorCategory`] and does not depend on a type parameter. To enable
11/// this, this struct in addition to an [`ErrorData`] value also contains an
12/// [`ErrorCodeFormatter`] function pointer belonging to the [`ErrorCategory`] of the most
13/// recent error.
14///
15/// All the limitation to error chaining outlined in [`Error`]'s documentation also apply
16/// to [`DynError`]. But because the [error category](ErrorCategory) type of this error is
17/// not known, whether or not it is possible to chain a [`DynError`] using
18/// [`chain()`](ChainError::chain()) or [`chain_err()`](crate::ResultChainError::chain_err())
19/// with an different `error_code` cannot be checked at compile time. So instead of a compile
20/// error it will cause a panic if unlinked [error categories](ErrorCategory) are chained.
21///
22/// ```
23/// # use embedded_error_chain::prelude::*;
24/// #[derive(Clone, Copy, ErrorCategory)]
25/// #[repr(u8)]
26/// enum FooError {
27///     Err
28/// }
29///
30/// #[derive(Clone, Copy, ErrorCategory)]
31/// #[error_category(links(FooError))]
32/// #[repr(u8)]
33/// enum BarError {
34///     Err
35/// }
36///
37/// fn cause_dyn_error() -> DynError {
38///     (FooError::Err).into()
39/// }
40///
41/// // Chain example
42/// fn do_chain() -> DynError {
43///     cause_dyn_error().chain(BarError::Err).into()
44/// }
45/// # do_chain();
46/// ```
47///
48/// This will panic:
49/// ```should_panic
50/// # use embedded_error_chain::prelude::*;
51/// #[derive(Clone, Copy, ErrorCategory)]
52/// #[repr(u8)]
53/// enum FooError {
54///     Err
55/// }
56///
57/// #[derive(Clone, Copy, ErrorCategory)]
58/// #[repr(u8)]
59/// enum BarError {
60///     Err
61/// }
62///
63/// fn cause_dyn_error() -> DynError {
64///     (FooError::Err).into()
65/// }
66///
67/// fn do_chain() -> DynError {
68///     cause_dyn_error().chain(BarError::Err).into()
69/// }
70///
71/// # do_chain();
72/// ```
73///
74/// Note also the `.into()` after chaining. This is required because chaining results in
75/// an [`Error<BarError>`] value being returned. This is also true for
76/// [`chain_err()`](crate::ResultChainError::chain_err()).
77///
78/// ```
79/// # use embedded_error_chain::prelude::*;
80/// # #[derive(Clone, Copy, ErrorCategory)]
81/// # #[repr(u8)]
82/// # enum FooError {
83/// #     Err
84/// # }
85/// #
86/// # #[derive(Clone, Copy, ErrorCategory)]
87/// # #[error_category(links(FooError))]
88/// # #[repr(u8)]
89/// # enum BarError {
90/// #     Err
91/// # }
92/// #
93/// fn cause_dyn_error_result() -> Result<(), DynError> {
94///     Err((FooError::Err).into())
95/// }
96///
97/// fn do_chain() -> Result<(), DynError> {
98///     cause_dyn_error_result().chain_err(BarError::Err)?;
99///     Ok(())
100/// }
101/// # do_chain();
102/// ```
103/// Here we use the property of the `?` operator that automatically converts the
104/// [`Error<BarError>`] to a [`DynError`] using `.into()`. This works because [`DynError`]
105/// implements the [`From`] trait for any [`Error<T>`](Error).
106///
107/// The following would not work and won't compile:
108/// ```compile_fail
109/// # use embedded_error_chain::prelude::*;
110/// # #[derive(Clone, Copy, ErrorCategory)]
111/// # #[repr(u8)]
112/// # enum FooError {
113/// #     Err
114/// # }
115/// #
116/// # #[derive(Clone, Copy, ErrorCategory)]
117/// # #[error_category(links(FooError))]
118/// # #[repr(u8)]
119/// # enum BarError {
120/// #     Err
121/// # }
122/// #
123/// # fn cause_dyn_error_result() -> Result<(), DynError> {
124/// #     Err((FooError::Err).into())
125/// # }
126/// #
127/// fn do_chain() -> Result<(), DynError> {
128///     cause_dyn_error_result().chain_err(BarError::Err)
129/// }
130/// # do_chain();
131/// ```
132///
133#[derive(Clone)]
134pub struct DynError {
135    error: ErrorData,
136    category_formatter: ErrorCodeFormatter,
137}
138
139impl PartialEq for DynError {
140    fn eq(&self, other: &DynError) -> bool {
141        self.error == other.error
142            && ptr::eq(
143                self.category_formatter as *const (),
144                other.category_formatter as *const (),
145            )
146    }
147}
148impl Eq for DynError {}
149
150impl DynError {
151    /// Create a [`DynError`] from an `error_code` belonging to [error
152    /// category](ErrorCategory) `C`.
153    #[inline]
154    pub fn new<C: ErrorCategory>(error_code: C) -> DynError {
155        DynError {
156            error: ErrorData::new(error_code.into()),
157            category_formatter: format_chained::<C>,
158        }
159    }
160
161    /// Create a [`DynError`] from its raw parts.
162    #[inline]
163    pub fn from_raw_parts(
164        error_data: ErrorData,
165        category_formatter: ErrorCodeFormatter,
166    ) -> DynError {
167        DynError {
168            error: error_data,
169            category_formatter,
170        }
171    }
172
173    /// Turn this dynamic error into its raw parts.
174    pub fn into_raw_parts(self) -> (ErrorData, ErrorCodeFormatter) {
175        (self.error, self.category_formatter)
176    }
177
178    /// Get the error code of the most recent error.
179    #[inline(always)]
180    pub fn code(&self) -> ErrorCode {
181        self.error.code()
182    }
183
184    /// Get the length of the error chain.
185    #[inline(always)]
186    pub fn chain_len(&self) -> usize {
187        self.error.chain_len()
188    }
189
190    /// Get the capacity of the error chain.
191    ///
192    /// Always returns [`ERROR_CHAIN_LEN`].
193    pub const fn chain_capacity(&self) -> usize {
194        ERROR_CHAIN_LEN
195    }
196
197    /// Get the [`ErrorCategoryHandle`] of the most recent error.
198    #[inline(always)]
199    pub fn category_handle(&self) -> ErrorCategoryHandle {
200        (self.category_formatter)(0, None, None).0
201    }
202
203    /// Get the [`ErrorCodeFormatter`] function of the most recent error.
204    #[inline(always)]
205    pub fn formatter(&self) -> ErrorCodeFormatter {
206        self.category_formatter
207    }
208
209    /// Return `true` if the most recent error code belongs to the [error category](ErrorCategory) `C`.
210    pub fn is<C: ErrorCategory>(&self) -> bool {
211        self.category_handle().is_handle_of::<C>()
212    }
213
214    /// Try to convert this untyped dynamic error into a statically typed error.
215    ///
216    /// Succeeds and returns the equivalent [`Error`] of this [`DynError`] if
217    /// [`self.is::<C>()`](Self::is()) returns `true`, otherwise returns an [`Err`]
218    /// containing the original [`DynError`].
219    pub fn try_into<C: ErrorCategory>(self) -> Result<crate::Error<C>, Self> {
220        if self.is::<C>() {
221            Ok(crate::Error::from_raw(self.error))
222        } else {
223            Err(self)
224        }
225    }
226
227    /// Query if this error was caused by `error_code` which belongs to the [error
228    /// category](ErrorCategory) `T`.
229    pub fn caused_by<T: ErrorCategory>(&self, error_code: T) -> bool {
230        let error_code: ErrorCode = error_code.into();
231        let category_handle = ErrorCategoryHandle::new::<T>();
232
233        self.iter()
234            .any(|(ec, handle)| handle == category_handle && ec == error_code)
235    }
236
237    /// Query the error code contained in this error that belongs to the (error
238    /// category)[`ErrorCategory`] `T`. Return `None` if this error was not caused by the
239    /// specified error category.
240    pub fn code_of_category<T: ErrorCategory>(&self) -> Option<T> {
241        let category_handle = ErrorCategoryHandle::new::<T>();
242        self.iter().find_map(|(ec, handle)| {
243            if handle == category_handle {
244                Some(ec.into())
245            } else {
246                None
247            }
248        })
249    }
250
251    /// Create an iterator that iterates over all error codes that caused this error.
252    #[inline]
253    pub fn iter(&self) -> ErrorIter {
254        ErrorIter {
255            formatter_func: Some(self.category_formatter),
256            curr_error_code: self.error.code(),
257            next_formatter_index: self.error.first_formatter_index(),
258            chain_iter: self.error.iter_chain(),
259        }
260    }
261
262    /// Try to chain this dynamically typed [`DynError`] with `error_code` of
263    /// [error category](ErrorCategory) `C`.
264    ///
265    /// A call to this function only succeeds if the slice of [`ErrorCodeFormatter`]s
266    /// returned by
267    /// [`C::chainable_category_formatters()`](ErrorCategory::chainable_category_formatters())
268    /// contains [`self.formatter()`](Self::formatter()).
269    ///
270    /// Note that this function has time complexity `O(n)` where `n` is the length of the
271    /// slice returned by
272    /// [`C::chainable_category_formatters()`](ErrorCategory::chainable_category_formatters()).
273    pub fn try_chain<C: ErrorCategory>(self, error_code: C) -> Result<Error<C>, Self> {
274        C::chainable_category_formatters()
275            .iter()
276            .enumerate()
277            .find_map(|(i, formatter)| {
278                if ptr::eq(
279                    *formatter as *const (),
280                    self.category_formatter as *const (),
281                ) {
282                    let mut data: ErrorData = self.error;
283                    ErrorData::chain(&mut data, error_code.into(), i as u8);
284                    Some(Error::from_raw(data))
285                } else {
286                    None
287                }
288            })
289            .ok_or(self)
290    }
291}
292
293impl<O: ErrorCategory> ChainError<O, DynError> for DynError {
294    /// Chain a [`DynError`] with any error code of a linked [`ErrorCategory`].
295    ///
296    /// Note that this function has complexity `O(n)` where `n` is the length of the slice
297    /// returned by
298    /// [`O::chainable_category_formatters()`](ErrorCategory::chainable_category_formatters()).
299    ///
300    /// ### Panics
301    /// A call to this function panics if the slice of [`ErrorCodeFormatter`]s
302    /// returned by
303    /// [`O::chainable_category_formatters()`](ErrorCategory::chainable_category_formatters())
304    /// does **not** contain [`self.formatter()`](DynError::formatter()).
305    fn chain(self, error_code: O) -> Error<O> {
306        self.try_chain(error_code)
307            .expect("cannot chain unlinked error categories")
308    }
309}
310
311impl fmt::Debug for DynError {
312    /// Debug format this error and its chain.
313    ///
314    /// Error message example:
315    /// ```txt
316    /// ControlTaskError(0): init failed
317    /// - ICM20689Error(0): init failed
318    /// - SpiError(0): bus error
319    /// ```
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        let (_, fmt_result) =
322            (self.category_formatter)(self.code(), self.error.first_formatter_index(), Some(f));
323
324        let mut formatter_func = fmt_result?;
325        for (ec, next_fmt_index) in self.error.iter_chain() {
326            formatter_func = if let Some(formatter_func) = formatter_func {
327                write!(f, "\n- ")?;
328                let (_, next_formatter) = formatter_func.into()(ec, next_fmt_index, Some(f));
329
330                next_formatter?
331            } else {
332                break;
333            };
334        }
335        Ok(())
336    }
337}
338
339impl<C: ErrorCategory> From<Error<C>> for DynError {
340    #[inline]
341    fn from(error: crate::Error<C>) -> Self {
342        DynError::from_raw_parts(error.into(), format_chained::<C>)
343    }
344}
345
346impl<C: ErrorCategory> From<C> for DynError {
347    #[inline]
348    fn from(error: C) -> Self {
349        DynError::from_raw_parts(ErrorData::new(error.into()), format_chained::<C>)
350    }
351}