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}