1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/*!
Easy error handling for embedded devices (no `liballoc` and `no_std`).

Errors are represented by error codes and come from enums that implement the
[`ErrorCategory`] trait (a derive macro exists), which is used for custom debug
printing per error code among other things. Each error code can have a value from `0`
to `15` (4 bits) and you can chain an error with up to four different error codes of
different categories.

The [`Error`] type encapsulates an error code and error chain, and is only a single
[`u32`] in size. There is also an untyped [`DynError`] type, which unlike [`Error`]
does not have a type parameter for the current error code. Its size is a [`u32`] +
pointer ([`usize`]), which can be used to forward source errors of different categories
to the caller.

This library was inspired by libraries such as
[error-chain](https://crates.io/crates/error-chain),
[anyhow](https://crates.io/crates/anyhow) and
[thiserror](https://crates.io/crates/thiserror), though it was made to work in `no_std`
**and** no `liballoc` environments with very little memory overhead.

## Example
```rust
use embedded_error_chain::prelude::*;

#[derive(Clone, Copy, ErrorCategory)]
#[repr(u8)]
enum SpiError {
    BusError,
    // ...
}

static LAST_GYRO_ACC_READOUT: usize = 200;

#[derive(Clone, Copy, ErrorCategory)]
#[error_category(links(SpiError))]
#[repr(u8)]
enum GyroAccError {
    InitFailed,

    #[error("{variant} (readout={})", LAST_GYRO_ACC_READOUT)]
    ReadoutFailed,

    /// Value must be in range [0, 256)
    #[error("{variant}: {summary}")]
    InvalidValue,
}

fn main() {
    if let Err(err) = calibrate() {
        // log the error
        println!("{:?}", err);
        // ...
    }

    let readout = match gyro_acc_readout() {
        Ok(val) => val,
        Err(err) => {
            if let Some(spi_error) = err.code_of_category::<SpiError>() {
                // try to fix it
                0
            }
            else {
                panic!("unfixable spi error");
            }
        }
    };
}

fn spi_init() -> Result<(), SpiError> {
    Err(SpiError::BusError)
}

fn gyro_acc_init() -> Result<(), Error<GyroAccError>> {
    spi_init().chain_err(GyroAccError::InitFailed)?;
    Ok(())
}

fn gyro_acc_readout() -> Result<u32, Error<GyroAccError>> {
    Err(SpiError::BusError.chain(GyroAccError::InvalidValue))
}

fn calibrate() -> Result<(), DynError> {
    gyro_acc_init()?;
    // other stuff...
    Ok(())
}
```
*/

#![no_std]
#![cfg_attr(feature = "nightly", feature(const_panic, const_fn))]
#![warn(missing_docs)]
#![allow(clippy::clippy::trivially_copy_pass_by_ref)]

#[cfg(feature = "std")]
extern crate std;

mod dyn_error;
mod error;
mod error_category;
mod error_data;

#[doc(hidden)]
pub mod utils;

pub use dyn_error::DynError;
pub use error::{ChainError, Error, ErrorIter, ResultChainError};
pub use error_category::{
    format_chained, ErrorCategory, ErrorCategoryHandle, ErrorCodeFormatter, ErrorCodeFormatterVal,
};
pub use error_data::{ErrorData, ERROR_CHAIN_LEN};

/// Everything for easy error handling.
pub mod prelude {
    #[doc(no_inline)]
    pub use crate::{
        ChainError, DynError, Error, ErrorCategory, ErrorCategoryHandle, ResultChainError,
    };
}

/// Marker types.
pub mod marker {
    /// A tag type to disambiguate between trait implementations.
    pub struct L0;
    /// A tag type to disambiguate between trait implementations.
    pub struct L1;
    /// A tag type to disambiguate between trait implementations.
    pub struct L2;
    /// A tag type to disambiguate between trait implementations.
    pub struct L3;
    /// A tag type to disambiguate between trait implementations.
    pub struct L4;
    /// A tag type to disambiguate between trait implementations.
    pub struct L5;

    /// A tag type to disambiguate between `ChainError` trait implementations for
    /// `Error<T>` and just for `T`.
    #[allow(non_camel_case_types)]
    pub struct Error_t;

    /// A tag type to disambiguate between `ChainError` trait implementations for
    /// `Error<T>` and just for `T`.
    #[allow(non_camel_case_types)]
    pub struct Concrete_t;

    pub use crate::error_category::Unused;
}

/// An error code belonging to an [`ErrorCategory`].
///
/// Must only be 4 bits wide.
pub type ErrorCode = u8;

/// Derive [`ErrorCategory`] for an enum.
///
/// This will also derive the trait dependencies of
/// [`ErrorCategory`](ErrorCategory) with the exception of
/// [`Copy`](core::marker::Copy):
/// - [`core::fmt::Debug`](core::fmt::Debug)
/// - [`Into`](core::convert::Into)`<`[`ErrorCode`](ErrorCode)`>`
/// - [`From`](core::convert::From)`<`[`ErrorCode`](ErrorCode)`>`
///
/// The traits `Into<ErrorCode>` and `From<ErrorCode>` are only derived if the enum is
/// `repr(u8)` (see
/// [`Data Layout` in the
/// nomicon](https://doc.rust-lang.org/nomicon/other-reprs.html#repru-repri)) or does *not*
/// contain any variants.
///
/// ## `#[error_category]` attribute
/// This attribute is optionally put once on the enum that is to be derived. It specifies
/// an optional [`ErrorCategory::NAME`] value (used for debug printing) and `0` to `6`
/// linked [`ErrorCategory`] types. If no `name` argument is given, the name of the enum
/// will be used for [`ErrorCategory::NAME`]. If no links are specified, the [error
/// category](ErrorCategory) is not linked.
///
/// **Example:**
/// ```
/// # use embedded_error_chain::prelude::*;
/// #
/// # #[derive(Clone, Copy, ErrorCategory)]
/// # enum Type0 {}
/// # #[derive(Clone, Copy, ErrorCategory)]
/// # enum Type1 {}
/// #
/// #[derive(Clone, Copy, ErrorCategory)]
/// #[error_category(name = "CustomName", links(Type0, Type1))]
/// #[repr(u8)]
/// enum FooError {
///     Error,
/// }
/// ```
///
/// ## `#[error]` attribute
/// This attribute is also optional and can be placed once above every enum variant.
/// Its arguments specify the arguments used for debug printing of an error code
/// represented by the variant.
///
/// Everything inside the paranthese (`#[error(...)]`) will directly be used as the
/// arguments of the [`write!()`] macro. So the attribute `#[error("fmt string {} {}",
/// GLOBAL_VAL, 5)]` will be translated to `write!(f, "fmt string {} {}", GLOBAL_VAL, 5)`.
/// The first argument must be a string literal and can contain special placeholders that
/// will be replaced by the derive macro:
///
/// - `{category}` will be replaced with the value of [`ErrorCategory::NAME`].
/// - `{variant}` will be replaced with the name of the variant.
/// - `{details}` will be replaced with the details section of the doc comments on the variant.
/// - `{summary}` will be replaced with the summary of the doc comments on the variant.
///
/// The summary section of the doc comments is all non-empty lines, ignoring all empty
/// lines until the first non-empty line, until an empty line or the end of the doc
/// comments. All the summary lines are then trimmed for whitespace and joined using a
/// space character (` `).
///
/// The details section of the doc comments is all lines (empty and non-empty) with the
/// first whitespace removed after the summary section and ignoring all empty-lines until
/// the first non-empty line.
///
/// **Example:**
/// ```text
/// <summmary> /// Summary starts here...
///            /// some more summary
/// </summary> /// ...and ends here.
///            ///
///            ///
/// <details>  /// Details start here...
///            ///
///            /// more details
/// </details> /// ...and end here.
/// ```
/// - summary:  
///     > ```text
///     > Summary starts here... some more summary ...and ends here.
///     > ```
///
/// - details:
///     > ```text
///     > Details start here...
///     >
///     > more details
///     > ...and end here.
///     > ```
///
/// If no `#[error]` attribute is put on the variant, then the summary part of the doc
/// comments will be used (see above). If the summary does not exist (no doc comments on
/// the variant) or is empty, then the variant name is used for debug printing.
///
/// ## Full example
///
/// ```rust
/// use embedded_error_chain::prelude::*;
///
/// #[derive(Clone, Copy, ErrorCategory)]
/// #[repr(u8)]
/// enum OtherError {
///     ExtremeFailure,
/// }
///
/// static SOME_GLOBAL_VARIABLE: usize = 200;
///
/// #[derive(Clone, Copy, ErrorCategory)]
/// #[error_category(name = "optional name", links(OtherError))]
/// #[repr(u8)]
/// enum TestError {
///     /// Foo error (summary)
///     ///
///     /// Detailed description.
///     /// The summary and detailed description are available as placeholders in
///     /// the `#[error(...)]` attribute. If no such attribute is put on the variant
///     /// or the `...` part is empty, then the summary will be used. If the summary
///     /// does not exist (no doc comments on the variant), then the variant name is
///     /// used for debug printing.
///     #[error("format string {summary}, {details}, {variant}, {category}")]
///     Foo = 0,
///
///     #[error("custom {}, {:?}", "some_expr", SOME_GLOBAL_VARIABLE)]
///     Other,
///
///     /// Some explanation explanation
///     Bar,
/// }
///
/// #[derive(Clone, Copy, ErrorCategory)]
/// #[error_category(links(OtherError, TestError))]
/// #[repr(u8)]
/// enum SeperateError {
///     SomethingHappened,
/// }
///
/// #[derive(Clone, Copy, ErrorCategory)]
/// enum YetEmptyError {}
/// ```
pub use embedded_error_chain_macros::ErrorCategory;