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}