embedded_error_chain/lib.rs
1/*!
2Easy error handling for embedded devices (no `liballoc` and `no_std`).
3
4Errors are represented by error codes and come from enums that implement the
5[`ErrorCategory`] trait (a derive macro exists), which is used for custom debug
6printing per error code among other things. Each error code can have a value from `0`
7to `15` (4 bits) and you can chain an error with up to four different error codes of
8different categories.
9
10The [`Error`] type encapsulates an error code and error chain, and is only a single
11[`u32`] in size. There is also an untyped [`DynError`] type, which unlike [`Error`]
12does not have a type parameter for the current error code. Its size is a [`u32`] +
13pointer ([`usize`]), which can be used to forward source errors of different categories
14to the caller.
15
16This library was inspired by libraries such as
17[error-chain](https://crates.io/crates/error-chain),
18[anyhow](https://crates.io/crates/anyhow) and
19[thiserror](https://crates.io/crates/thiserror), though it was made to work in `no_std`
20**and** no `liballoc` environments with very little memory overhead.
21
22## Example
23```rust
24use embedded_error_chain::prelude::*;
25
26#[derive(Clone, Copy, ErrorCategory)]
27#[repr(u8)]
28enum SpiError {
29 BusError,
30 // ...
31}
32
33static LAST_GYRO_ACC_READOUT: usize = 200;
34
35#[derive(Clone, Copy, ErrorCategory)]
36#[error_category(links(SpiError))]
37#[repr(u8)]
38enum GyroAccError {
39 InitFailed,
40
41 #[error("{variant} (readout={})", LAST_GYRO_ACC_READOUT)]
42 ReadoutFailed,
43
44 /// Value must be in range [0, 256)
45 #[error("{variant}: {summary}")]
46 InvalidValue,
47}
48
49fn main() {
50 if let Err(err) = calibrate() {
51 // log the error
52 println!("{:?}", err);
53 // ...
54 }
55
56 let readout = match gyro_acc_readout() {
57 Ok(val) => val,
58 Err(err) => {
59 if let Some(spi_error) = err.code_of_category::<SpiError>() {
60 // try to fix it
61 0
62 }
63 else {
64 panic!("unfixable spi error");
65 }
66 }
67 };
68}
69
70fn spi_init() -> Result<(), SpiError> {
71 Err(SpiError::BusError)
72}
73
74fn gyro_acc_init() -> Result<(), Error<GyroAccError>> {
75 spi_init().chain_err(GyroAccError::InitFailed)?;
76 Ok(())
77}
78
79fn gyro_acc_readout() -> Result<u32, Error<GyroAccError>> {
80 Err(SpiError::BusError.chain(GyroAccError::InvalidValue))
81}
82
83fn calibrate() -> Result<(), DynError> {
84 gyro_acc_init()?;
85 // other stuff...
86 Ok(())
87}
88```
89*/
90
91#![no_std]
92#![cfg_attr(feature = "nightly", feature(const_panic, const_fn))]
93#![warn(missing_docs)]
94#![allow(clippy::clippy::trivially_copy_pass_by_ref)]
95
96#[cfg(feature = "std")]
97extern crate std;
98
99mod dyn_error;
100mod error;
101mod error_category;
102mod error_data;
103
104#[doc(hidden)]
105pub mod utils;
106
107pub use dyn_error::DynError;
108pub use error::{ChainError, Error, ErrorIter, ResultChainError};
109pub use error_category::{
110 format_chained, ErrorCategory, ErrorCategoryHandle, ErrorCodeFormatter, ErrorCodeFormatterVal,
111};
112pub use error_data::{ErrorData, ERROR_CHAIN_LEN};
113
114/// Everything for easy error handling.
115pub mod prelude {
116 #[doc(no_inline)]
117 pub use crate::{
118 ChainError, DynError, Error, ErrorCategory, ErrorCategoryHandle, ResultChainError,
119 };
120}
121
122/// Marker types.
123pub mod marker {
124 /// A tag type to disambiguate between trait implementations.
125 pub struct L0;
126 /// A tag type to disambiguate between trait implementations.
127 pub struct L1;
128 /// A tag type to disambiguate between trait implementations.
129 pub struct L2;
130 /// A tag type to disambiguate between trait implementations.
131 pub struct L3;
132 /// A tag type to disambiguate between trait implementations.
133 pub struct L4;
134 /// A tag type to disambiguate between trait implementations.
135 pub struct L5;
136
137 /// A tag type to disambiguate between `ChainError` trait implementations for
138 /// `Error<T>` and just for `T`.
139 #[allow(non_camel_case_types)]
140 pub struct Error_t;
141
142 /// A tag type to disambiguate between `ChainError` trait implementations for
143 /// `Error<T>` and just for `T`.
144 #[allow(non_camel_case_types)]
145 pub struct Concrete_t;
146
147 pub use crate::error_category::Unused;
148}
149
150/// An error code belonging to an [`ErrorCategory`].
151///
152/// Must only be 4 bits wide.
153pub type ErrorCode = u8;
154
155/// Derive [`ErrorCategory`] for an enum.
156///
157/// This will also derive the trait dependencies of
158/// [`ErrorCategory`](ErrorCategory) with the exception of
159/// [`Copy`](core::marker::Copy):
160/// - [`core::fmt::Debug`](core::fmt::Debug)
161/// - [`Into`](core::convert::Into)`<`[`ErrorCode`](ErrorCode)`>`
162/// - [`From`](core::convert::From)`<`[`ErrorCode`](ErrorCode)`>`
163///
164/// The traits `Into<ErrorCode>` and `From<ErrorCode>` are only derived if the enum is
165/// `repr(u8)` (see
166/// [`Data Layout` in the
167/// nomicon](https://doc.rust-lang.org/nomicon/other-reprs.html#repru-repri)) or does *not*
168/// contain any variants.
169///
170/// ## `#[error_category]` attribute
171/// This attribute is optionally put once on the enum that is to be derived. It specifies
172/// an optional [`ErrorCategory::NAME`] value (used for debug printing) and `0` to `6`
173/// linked [`ErrorCategory`] types. If no `name` argument is given, the name of the enum
174/// will be used for [`ErrorCategory::NAME`]. If no links are specified, the [error
175/// category](ErrorCategory) is not linked.
176///
177/// **Example:**
178/// ```
179/// # use embedded_error_chain::prelude::*;
180/// #
181/// # #[derive(Clone, Copy, ErrorCategory)]
182/// # enum Type0 {}
183/// # #[derive(Clone, Copy, ErrorCategory)]
184/// # enum Type1 {}
185/// #
186/// #[derive(Clone, Copy, ErrorCategory)]
187/// #[error_category(name = "CustomName", links(Type0, Type1))]
188/// #[repr(u8)]
189/// enum FooError {
190/// Error,
191/// }
192/// ```
193///
194/// ## `#[error]` attribute
195/// This attribute is also optional and can be placed once above every enum variant.
196/// Its arguments specify the arguments used for debug printing of an error code
197/// represented by the variant.
198///
199/// Everything inside the paranthese (`#[error(...)]`) will directly be used as the
200/// arguments of the [`write!()`] macro. So the attribute `#[error("fmt string {} {}",
201/// GLOBAL_VAL, 5)]` will be translated to `write!(f, "fmt string {} {}", GLOBAL_VAL, 5)`.
202/// The first argument must be a string literal and can contain special placeholders that
203/// will be replaced by the derive macro:
204///
205/// - `{category}` will be replaced with the value of [`ErrorCategory::NAME`].
206/// - `{variant}` will be replaced with the name of the variant.
207/// - `{details}` will be replaced with the details section of the doc comments on the variant.
208/// - `{summary}` will be replaced with the summary of the doc comments on the variant.
209///
210/// The summary section of the doc comments is all non-empty lines, ignoring all empty
211/// lines until the first non-empty line, until an empty line or the end of the doc
212/// comments. All the summary lines are then trimmed for whitespace and joined using a
213/// space character (` `).
214///
215/// The details section of the doc comments is all lines (empty and non-empty) with the
216/// first whitespace removed after the summary section and ignoring all empty-lines until
217/// the first non-empty line.
218///
219/// **Example:**
220/// ```text
221/// <summmary> /// Summary starts here...
222/// /// some more summary
223/// </summary> /// ...and ends here.
224/// ///
225/// ///
226/// <details> /// Details start here...
227/// ///
228/// /// more details
229/// </details> /// ...and end here.
230/// ```
231/// - summary:
232/// > ```text
233/// > Summary starts here... some more summary ...and ends here.
234/// > ```
235///
236/// - details:
237/// > ```text
238/// > Details start here...
239/// >
240/// > more details
241/// > ...and end here.
242/// > ```
243///
244/// If no `#[error]` attribute is put on the variant, then the summary part of the doc
245/// comments will be used (see above). If the summary does not exist (no doc comments on
246/// the variant) or is empty, then the variant name is used for debug printing.
247///
248/// ## Full example
249///
250/// ```rust
251/// use embedded_error_chain::prelude::*;
252///
253/// #[derive(Clone, Copy, ErrorCategory)]
254/// #[repr(u8)]
255/// enum OtherError {
256/// ExtremeFailure,
257/// }
258///
259/// static SOME_GLOBAL_VARIABLE: usize = 200;
260///
261/// #[derive(Clone, Copy, ErrorCategory)]
262/// #[error_category(name = "optional name", links(OtherError))]
263/// #[repr(u8)]
264/// enum TestError {
265/// /// Foo error (summary)
266/// ///
267/// /// Detailed description.
268/// /// The summary and detailed description are available as placeholders in
269/// /// the `#[error(...)]` attribute. If no such attribute is put on the variant
270/// /// or the `...` part is empty, then the summary will be used. If the summary
271/// /// does not exist (no doc comments on the variant), then the variant name is
272/// /// used for debug printing.
273/// #[error("format string {summary}, {details}, {variant}, {category}")]
274/// Foo = 0,
275///
276/// #[error("custom {}, {:?}", "some_expr", SOME_GLOBAL_VARIABLE)]
277/// Other,
278///
279/// /// Some explanation explanation
280/// Bar,
281/// }
282///
283/// #[derive(Clone, Copy, ErrorCategory)]
284/// #[error_category(links(OtherError, TestError))]
285/// #[repr(u8)]
286/// enum SeperateError {
287/// SomethingHappened,
288/// }
289///
290/// #[derive(Clone, Copy, ErrorCategory)]
291/// enum YetEmptyError {}
292/// ```
293pub use embedded_error_chain_macros::ErrorCategory;