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}