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;