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}