embedded_error_chain/
error_category.rs

1use crate::ErrorCode;
2
3use core::{
4    fmt::{self, Debug, Formatter},
5    ptr,
6};
7
8/// A chained formatter function for a single error category.
9///
10/// A single `ErrorCodeFormatter` function is considered to be uniquely associated with a
11/// type that implements [`ErrorCategory`]. Meaning one such function only ever returns the
12/// [`ErrorCategoryHandle`] for that associated [`ErrorCategory`], and never for another.
13///
14/// This function serves multiple purposes:
15/// 1. If `f` is [`Some`] then this functions formats `error_code` using `f`.
16/// 2. If `next_formatter` is `Some(index)` then it returns the chained formatter of the
17///    associated [`ErrorCategory`] indexed by `index`. A `Some(`[`ErrorCodeFormatterVal`]`)` is
18///    returned if `index` is within bounds of the chainable categories (see
19///    [`ErrorCategory::chainable_category_formatters()`]).
20/// 3. This function additionally always returns a [`ErrorCategoryHandle`] that represents
21///    the associated [`ErrorCategory`].
22pub type ErrorCodeFormatter = fn(
23    error_code: ErrorCode,
24    next_formatter: Option<u8>,
25    f: Option<&mut Formatter<'_>>,
26) -> (
27    ErrorCategoryHandle,
28    Result<Option<ErrorCodeFormatterVal>, fmt::Error>,
29);
30
31/// A wrapped [`ErrorCodeFormatter`] value.
32///
33/// This is returned from the [`ErrorCodeFormatter`] function itself as a workaround,
34/// because function type definitions cannot reference themselves. The contained function
35/// is actually also a [`ErrorCodeFormatter`] value.
36#[repr(transparent)]
37pub struct ErrorCodeFormatterVal(ErrorCodeFormatter);
38
39impl ErrorCodeFormatterVal {
40    /// Create a new wrapped [`ErrorCodeFormatter`] value from `func`.
41    pub fn new(func: ErrorCodeFormatter) -> ErrorCodeFormatterVal {
42        ErrorCodeFormatterVal(func)
43    }
44
45    /// Unwrap the wrapped [`ErrorCodeFormatter`] value.
46    pub fn into(self) -> ErrorCodeFormatter {
47        self.0
48    }
49}
50
51/// A trait that implements the logic for debug printing and [`ErrorCode`] conversion. It
52/// also specifies the links to other error categories that allows errors of
53/// different categories to be chained.
54///
55/// Note: Only up to 6 linked error categories are supported.
56///
57/// See [`Error`](crate::Error), [`DynError`](crate::DynError) and
58/// [`ErrorData`](crate::ErrorData) for more information.
59pub trait ErrorCategory: Copy + Into<ErrorCode> + From<ErrorCode> + Debug {
60    /// The text name of this category used for formatting.
61    const NAME: &'static str;
62
63    /// Type of linked error category 0.
64    ///
65    /// Set to [`Unused`] if unused.
66    type L0: ErrorCategory;
67    /// Type of linked error category 1.
68    ///
69    /// Set to [`Unused`] if unused.
70    type L1: ErrorCategory;
71    /// Type of linked error category 2.
72    ///
73    /// Set to [`Unused`] if unused.
74    type L2: ErrorCategory;
75    /// Type of linked error category 3.
76    ///
77    /// Set to [`Unused`] if unused.
78    type L3: ErrorCategory;
79    /// Type of linked error category 4.
80    ///
81    /// Set to [`Unused`] if unused.
82    type L4: ErrorCategory;
83    /// Type of linked error category 5.
84    ///
85    /// Set to [`Unused`] if unused.
86    type L5: ErrorCategory;
87
88    /// Get a slice of all [`ErrorCodeFormatter`] functions for all [error
89    /// categories](ErrorCategory) that this [error category](ErrorCategory) is linked to.
90    ///
91    /// Specifically returns a slice of function pointers to the error code formatter
92    /// function of [`Self::L0`] up to [`Self::L5`]. Each element in the returned slice
93    /// corresponds to the formatter function of the [error category](ErrorCategory) type
94    /// `Self::Lx` where `x` is the index of the element. The slice can be smaller than 6
95    /// elements, if the excluded linked error categories are unused (i.e. `Self::Lx` is
96    /// set to [`Unused`]).
97    ///
98    /// All formatter functions contained in the returned slice must have identical
99    /// behavior to [`format_chained()`] with the exception that the formatting can
100    /// differ.
101    fn chainable_category_formatters() -> &'static [ErrorCodeFormatter] {
102        &[
103            format_chained::<Self::L0>,
104            format_chained::<Self::L1>,
105            format_chained::<Self::L2>,
106            format_chained::<Self::L3>,
107            format_chained::<Self::L4>,
108            format_chained::<Self::L5>,
109        ]
110    }
111}
112
113/// A handle to a type that implements [`ErrorCategory`].
114#[derive(Debug)]
115pub struct ErrorCategoryHandle {
116    name: &'static str,
117    chainable_category_formatters: fn() -> &'static [ErrorCodeFormatter],
118}
119
120impl ErrorCategoryHandle {
121    /// Create a new handle from the type parameter `C`.
122    pub fn new<C: ErrorCategory>() -> ErrorCategoryHandle {
123        Self {
124            name: C::NAME,
125            chainable_category_formatters: C::chainable_category_formatters,
126        }
127    }
128
129    /// Get the name of this associated [`ErrorCategory`].
130    pub fn name(&self) -> &'static str {
131        self.name
132    }
133
134    /// Check whether this handle is a handle of the [`ErrorCategory`] `C`.
135    #[inline]
136    pub fn is_handle_of<C: ErrorCategory>(&self) -> bool {
137        ptr::eq(self.name.as_ptr(), C::NAME.as_ptr())
138            && ptr::eq(
139                self.chainable_category_formatters as *const (),
140                C::chainable_category_formatters as *const (),
141            )
142    }
143}
144
145impl PartialEq for ErrorCategoryHandle {
146    fn eq(&self, other: &ErrorCategoryHandle) -> bool {
147        ptr::eq(self.name.as_ptr(), other.name.as_ptr())
148            && ptr::eq(
149                self.chainable_category_formatters as *const (),
150                other.chainable_category_formatters as *const (),
151            )
152    }
153}
154impl Eq for ErrorCategoryHandle {}
155
156/// Debug format the given `error_code` using `f` if `f` is `Some`, get the
157/// [`ErrorCategoryHandle`] of the type parameter `C`, and get the next [`ErrorCodeFormatter`]
158/// if `next_formatter` is `Some`.
159///
160/// If `f` is `Some()` the following format is used:  
161///    `{C::NAME}({error_code}): {<error_code as C>:?}`
162pub fn format_chained<C: ErrorCategory>(
163    error_code: ErrorCode,
164    next_formatter: Option<u8>,
165    f: Option<&mut Formatter<'_>>,
166) -> (
167    ErrorCategoryHandle,
168    Result<Option<ErrorCodeFormatterVal>, fmt::Error>,
169) {
170    let fmt_res = if let Some(f) = f {
171        let err: C = error_code.into();
172        write!(f, "{}({}): {:?}", C::NAME, error_code, err)
173    } else {
174        Ok(())
175    };
176
177    (
178        ErrorCategoryHandle::new::<C>(),
179        fmt_res.map(|_| {
180            // Get the next formatter function if `next_formatter` is `Some`.
181            next_formatter.and_then(|idx| {
182                let idx = idx as usize;
183                let formatters = C::chainable_category_formatters();
184
185                if idx < formatters.len() {
186                    Some(ErrorCodeFormatterVal::new(formatters[idx]))
187                } else {
188                    None
189                }
190            })
191        }),
192    )
193}
194
195/// This marker type is used for any [`ErrorCategory::L0`] to [`ErrorCategory::L5`]
196/// which is unused.
197#[derive(Debug, Clone, Copy)]
198pub enum Unused {}
199
200impl ErrorCategory for Unused {
201    const NAME: &'static str = "";
202    type L0 = Unused;
203    type L1 = Unused;
204    type L2 = Unused;
205    type L3 = Unused;
206    type L4 = Unused;
207    type L5 = Unused;
208
209    fn chainable_category_formatters() -> &'static [ErrorCodeFormatter] {
210        &[]
211    }
212}
213
214impl From<ErrorCode> for Unused {
215    fn from(_: ErrorCode) -> Self {
216        unreachable!()
217    }
218}
219
220impl Into<ErrorCode> for Unused {
221    fn into(self) -> ErrorCode {
222        match self {}
223    }
224}