embedded_error_chain/
error_data.rs

1#[allow(unused_imports)]
2use crate::ErrorCategory;
3use crate::ErrorCode;
4
5/// The maximum amount of error codes that can be chained to an [`Error`](crate::Error) or
6/// [`DynError`](crate::DynError).
7///
8/// This is equal to the amount of [`ErrorData::chain()`] (or
9/// [`ChainError::chain()`](super::ChainError::chain()),
10/// [`ResultChainError::chain_err()`](super::ResultChainError::chain_err())) you can make
11/// before the chain overflows, and it either panics (if the feature `panic-on-overflow`
12/// is enabled) or the oldest error code gets lost.
13pub const ERROR_CHAIN_LEN: usize = 4;
14/// The entire data of the error and its error code chain.
15///
16/// This is a wrapper over a bit-packed [`u32`] value that contains five 4-bit wide
17/// [`ErrorCode`](crate::ErrorCode)s and four 3-bit wide
18/// [`ErrorCodeFormatter`](crate::ErrorCodeFormatter) indices.
19///
20/// The bit layout of the underlying `u32` value is a follows:
21/// - Bits `b0..b20` contain 5 error codes, each error code is 4 bits.
22///   - `b0..b4`: the error code of the current error (returned by [`code()`](Self::code()))
23///   - `b4..b8`: chained error code 0
24///   - `b8..b12`: chained error code 1
25///   - `b12..b16`: chained error code 2
26///   - `b16..b20`: chained error code 3
27/// - Bits `b20..b32` contain 4 formatter indices, each index has 3 bits.
28///   - `b20..b23`: formatter `index + 1` of chained error 0 (`0` means not present)
29///                 (returned by [`first_formatter_index()`](Self::first_formatter_index()))
30///   - `b23..b26`: formatter `index + 1` of chained error 1 (`0` means not present)
31///   - `b26..b29`: formatter `index + 1` of chained error 2 (`0` means not present)
32///   - `b29..b32`: formatter `index + 1` of chained error 3 (`0` means not present)
33///
34/// The first [error code](crate::ErrorCode) represents the most recent or current error.
35/// The next four [error codes](crate::ErrorCode) with the formatter indices represent the
36/// error chain which can be empty. The error chain (as described in the documentation of
37/// [`Error`](crate::Error)) is a singly linked list. As much of the data used for error
38/// reporting is constant or static, so that no dynamic allocation is needed, to make
39/// runtime memory usage as small as possible and to make it cheap to copy an error value
40/// around. This is also the case with the error chain.
41///
42/// Every [`ErrorCode`] value belongs to a type that implements the trait
43/// [`ErrorCategory`]. Using this trait it is possible to print a custom name and
44/// additional information for every [`ErrorCode`] value. Only the [`ErrorCategory`] of
45/// the most recent error code has to be known, all other [error
46/// categories](ErrorCategory) can then be retrieved by iterating over the linked list.
47/// The [`ErrorCategory`] is also needed for the linked list to be possible.
48///
49/// Every formatter index in the chain represents the
50/// [`ErrorCodeFormatter`](crate::ErrorCodeFormatter) function of the [error
51/// category](ErrorCategory) and error code. A formatter function is retrieved by calling
52/// the formatter function of the previous error code, and passing it the index of the
53/// next formatter function. The called formatter function gets the next formatter
54/// function from from the slice returned by
55/// [`ErrorCategory::chainable_category_formatters()`] using the next formatter index.
56/// This is only possible if the [`ErrorCategory`] associated with the called formatter
57/// function is linked to the [`ErrorCategory`] of the next error code in the chain.
58///
59/// An [error category](ErrorCategory) `A` is linked to an [error category](ErrorCategory)
60/// `B` if at least one of the [`A::L1`](ErrorCategory::L1) to
61/// [`A::L5`](ErrorCategory::L1) associated types is `B` and the `n`th element (where `n`
62/// is the digit of the [`A::Ln`](ErrorCategory::L1) associated type used) of the slice
63/// returned by
64/// [`A::chainable_category_formatters()`](ErrorCategory::chainable_category_formatters())
65/// is the [`ErrorCodeFormatter`](crate::ErrorCodeFormatter) function for `B`.
66#[derive(Clone, Copy, PartialEq, Eq)]
67#[repr(transparent)]
68pub struct ErrorData {
69    /// Contains the entire data of the error and its error code chain.
70    ///
71    /// - Bits `b0..b20` contain 5 error codes, each error code is 4 bits.
72    ///   - `b0..b4`: the error code of the current error (returned by `Self::code()`)
73    ///   - `b4..b8`: chained error code 0
74    ///   - `b8..b12`: chained error code 1
75    ///   - `b12..b16`: chained error code 2
76    ///   - `b16..b20`: chained error code 3
77    /// - Bits `b20..b32` contain 4 formatter indices, each index has 3 bits.
78    ///   - `b20..b23`: formatter `index + 1` of chained error 0 (`0` means not present)
79    ///                 (returned by `Self::first_formatter_index()`)
80    ///   - `b23..b26`: formatter `index + 1` of chained error 1 (`0` means not present)
81    ///   - `b26..b29`: formatter `index + 1` of chained error 2 (`0` means not present)
82    ///   - `b29..b32`: formatter `index + 1` of chained error 3 (`0` means not present)
83    data: u32,
84}
85
86mod consts {
87    pub const CODE_MASK: [u32; 5] = [
88        0x0000_000f,
89        0x0000_00f0,
90        0x0000_0f00,
91        0x0000_f000,
92        0x000f_0000,
93    ];
94    pub const ALL_CODE_MASK: u32 = 0x000f_ffff;
95    /// A error code has 4 bits.
96    pub const CODE_WIDTH: u32 = 4;
97
98    #[inline(always)]
99    pub const fn make_code(value: super::ErrorCode) -> u32 {
100        (value & 0b1111) as u32
101    }
102
103    pub const FORMATTER_MASK: [u32; 4] = [0x0070_0000, 0x0380_0000, 0x1c00_0000, 0xe000_0000];
104    pub const ALL_FORMATTER_MASK: u32 = 0xfff0_0000;
105    /// The first formatter index begins at bit 20.
106    pub const FORMATTER_BITOFFSET: u32 = 20;
107    /// A formatter index has 3 bits.
108    pub const FORMATTER_IDX_WIDTH: u32 = 3;
109
110    #[inline(always)]
111    pub const fn make_formatter_idx(value: u8) -> u32 {
112        (value & 0b0111) as u32
113    }
114}
115
116impl ErrorData {
117    /// Create new `ErrorData` that contains the supplied `error_code` and has an empty chain.
118    pub const fn new(error_code: ErrorCode) -> ErrorData {
119        ErrorData {
120            data: error_code as u32 & consts::CODE_MASK[0],
121        }
122    }
123
124    /// Replace the error code with `code` and return the old one.     
125    ///
126    /// Note: That the categories of the new error code and the old must be the same.
127    pub fn set_code(&mut self, code: ErrorCode) -> ErrorCode {
128        let old_ec = consts::make_code(self.data as u8) as ErrorCode;
129        self.data = (self.data & !(consts::CODE_MASK[0])) | consts::make_code(code);
130        old_ec
131    }
132
133    /// Get the most recent error code of the error.
134    #[inline]
135    pub fn code(&self) -> ErrorCode {
136        (self.data & consts::CODE_MASK[0]) as ErrorCode
137    }
138
139    /// Get the first formatter index in the chain if available.
140    pub fn first_formatter_index(&self) -> Option<u8> {
141        let fmt_index =
142            ((self.data & consts::FORMATTER_MASK[0]) >> consts::FORMATTER_BITOFFSET) as u8;
143        if fmt_index > 0 {
144            Some(fmt_index - 1)
145        } else {
146            None
147        }
148    }
149
150    /// Get the number of chained error codes.
151    pub fn chain_len(&self) -> usize {
152        // If the formatter is zero that means it is not present.
153        let mut mask = consts::FORMATTER_MASK[0];
154
155        for fmt_index in 0..ERROR_CHAIN_LEN {
156            if (self.data & mask) == 0 {
157                return fmt_index;
158            }
159            mask <<= consts::FORMATTER_IDX_WIDTH;
160        }
161        ERROR_CHAIN_LEN
162    }
163
164    /// Whether the error chain is full.
165    #[inline]
166    pub fn chain_full(&self) -> bool {
167        self.chain_len() == ERROR_CHAIN_LEN
168    }
169
170    /// Prepend the current error code to the front of the error chain and set the current error
171    /// code to `error_code`.
172    ///
173    /// Returns the back of the error chain before modification if it gets overwritten by
174    /// this operation (when the chain overflows).
175    ///
176    /// Note: `error_code` is masked to the first 4 bits and `category_index` is masked to
177    /// the first 3 bits.
178    pub fn push_front(
179        &mut self,
180        error_code: ErrorCode,
181        category_index: u8,
182    ) -> Option<(ErrorCode, u8)> {
183        // Get the last error code and formatter index in the chain,
184        // if the formatter index is greater `0` that means the chain is full
185        // and we return these from the function.
186        let fmt_index_back = self.data & consts::FORMATTER_MASK[ERROR_CHAIN_LEN - 1];
187        let result = if fmt_index_back > 0 {
188            let ec_back = (self.data & consts::CODE_MASK[ERROR_CHAIN_LEN])
189                >> (ERROR_CHAIN_LEN as u32 * consts::CODE_WIDTH);
190            let fmt_index_back = fmt_index_back
191                >> ((ERROR_CHAIN_LEN as u32 - 1) * consts::FORMATTER_IDX_WIDTH
192                    + consts::FORMATTER_BITOFFSET);
193
194            Some((ec_back as ErrorCode, (fmt_index_back - 1) as u8))
195        } else {
196            None
197        };
198
199        let fmt_indices = ((self.data & consts::ALL_FORMATTER_MASK) << consts::FORMATTER_IDX_WIDTH)
200            | (consts::make_formatter_idx(category_index + 1) << consts::FORMATTER_BITOFFSET);
201
202        let err_codes = ((self.data << consts::CODE_WIDTH) & consts::ALL_CODE_MASK)
203            | consts::make_code(error_code);
204
205        self.data = fmt_indices | err_codes;
206
207        result
208    }
209
210    /// Chain this error with a new error specified by `error_code`.
211    ///
212    /// - `error_code`: The new error code that is set as the current one.
213    /// - `category_index`: The index of the
214    ///   [`ErrorCodeFormatter`](crate::ErrorCodeFormatter) in the slice returned by
215    ///   [`T::chainable_category_formatters()`](ErrorCategory::chainable_category_formatters())
216    ///   where `T` is the [`error category`](ErrorCategory) that the most recent error code
217    ///   before this operation belongs to.
218    ///
219    /// This prepends the current error code to the front of the error chain and sets
220    /// `error_code` as the new current error code.
221    ///
222    /// ### Panics
223    /// If the feature `panic-on-overflow` is enabled and the error chain is already full
224    /// before this operation, this function will panic. If the feature is not enabled and
225    /// the error chain is already full, the last error in the chain will be lost.
226    pub fn chain(&mut self, error_code: ErrorCode, category_index: u8) {
227        // Returns the last error in the chain if it's full.
228        let overflow = self.push_front(error_code, category_index);
229
230        #[cfg(feature = "panic-on-overflow")]
231        debug_assert!(
232            overflow.is_none(),
233            "chaining two errors overflowed; error chain is full"
234        );
235    }
236
237    /// Iterate over the error chain.
238    pub(crate) fn iter_chain(&self) -> ErrorDataChainIter {
239        ErrorDataChainIter {
240            error_codes: (self.data & consts::ALL_CODE_MASK) >> consts::CODE_WIDTH,
241            formatters: (self.data & consts::ALL_FORMATTER_MASK) >> consts::FORMATTER_BITOFFSET,
242        }
243    }
244}
245
246/// An iterator over the error chain.
247///
248/// For every iteration a tuple is returned which contains:
249/// - `0`: The error code at the current chain position.
250/// - `1`: The formatter index of the next chain position if present.
251pub(crate) struct ErrorDataChainIter {
252    error_codes: u32,
253    formatters: u32,
254}
255
256impl Iterator for ErrorDataChainIter {
257    type Item = (ErrorCode, Option<u8>);
258
259    fn next(&mut self) -> Option<Self::Item> {
260        if self.formatters > 0 {
261            let ec = self.error_codes & consts::CODE_MASK[0];
262            self.error_codes >>= consts::CODE_WIDTH;
263            self.formatters >>= consts::FORMATTER_IDX_WIDTH;
264
265            let next_fmt_index = {
266                let next_fmt_index = consts::make_formatter_idx(self.formatters as u8);
267                if next_fmt_index > 0 {
268                    Some(next_fmt_index as u8 - 1)
269                } else {
270                    None
271                }
272            };
273
274            Some((ec as ErrorCode, next_fmt_index))
275        } else {
276            None
277        }
278    }
279}