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;