error_code/
lib.rs

1//! Error code library provides generic errno/winapi error wrapper
2//!
3//! User can define own [Category](struct.Category.html) if you want to create new error wrapper.
4//!
5//! ## Usage
6//!
7//! ```rust
8//! use error_code::ErrorCode;
9//!
10//! use std::fs::File;
11//!
12//! File::open("non_existing");
13//! println!("{}", ErrorCode::last_system());
14//! ```
15
16#![no_std]
17#![warn(missing_docs)]
18#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
19
20#[cfg(feature = "std")]
21extern crate std;
22
23use core::{mem, hash, fmt};
24
25#[deprecated]
26///Text to return when cannot map error
27pub const UNKNOWN_ERROR: &str = "Unknown error";
28///Text to return when error fails to be converted into utf-8
29pub const FAIL_ERROR_FORMAT: &str = "Failed to format error into utf-8";
30
31///Error message buffer size
32pub const MESSAGE_BUF_SIZE: usize = 256;
33///Type alias for buffer to hold error code description.
34pub type MessageBuf = [mem::MaybeUninit<u8>; MESSAGE_BUF_SIZE];
35
36pub mod defs;
37pub mod types;
38pub mod utils;
39mod posix;
40pub use posix::POSIX_CATEGORY;
41mod system;
42pub use system::SYSTEM_CATEGORY;
43
44#[macro_export]
45///Defines error code `Category` as enum which implements conversion into generic ErrorCode
46///
47///This enum shall implement following traits:
48///
49///- `Clone`
50///- `Copy`
51///- `Debug`
52///- `Display` - uses `ErrorCode` `fmt::Display`
53///- `PartialEq` / `Eq`
54///- `PartialOrd` / `Ord`
55///
56///# Usage
57///
58///```
59///use error_code::{define_category, ErrorCode};
60///
61///define_category!(
62///    ///This is documentation for my error
63///    ///
64///    ///Documentation of variants only allow 1 line comment and it should be within 256 characters
65///    pub enum MyError {
66///        ///Success
67///        Success = 0,
68///        ///This is bad
69///        Error = 1,
70///    }
71///);
72///
73///fn handle_error(res: Result<(), MyError>) -> Result<(), ErrorCode> {
74///    res?;
75///    Ok(())
76///}
77///
78///let error = handle_error(Err(MyError::Error)).expect_err("Should return error");
79///assert_eq!(error.to_string(), "MyError(1): This is bad");
80///assert_eq!(error.to_string(), MyError::Error.to_string());
81///```
82macro_rules! define_category {
83    (
84        $(#[$docs:meta])*
85        pub enum $name:ident {
86            $(
87                #[doc = $msg:literal]
88                $ident:ident = $code:literal,
89             )+
90        }
91    ) => {
92        #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord)]
93        #[repr(i32)]
94        $(#[$docs])*
95        pub enum $name {
96            $(
97                #[doc = $msg]
98                $ident = $code,
99            )+
100        }
101
102        impl From<$name> for $crate::ErrorCode {
103            #[inline(always)]
104            fn from(this: $name) -> $crate::ErrorCode {
105                this.into_error_code()
106            }
107        }
108
109        impl core::fmt::Display for $name {
110            #[inline(always)]
111            fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
112                core::fmt::Display::fmt(&self.into_error_code(), fmt)
113            }
114        }
115
116        impl $name {
117            const _ASSERT: () = {
118                $(
119                    assert!($msg.len() <= $crate::MESSAGE_BUF_SIZE, "Message buffer overflow, make sure your messages are not beyond MESSAGE_BUF_SIZE");
120                )+
121            };
122
123
124            #[inline(always)]
125            ///Map raw error code to textual representation.
126            pub fn map_code(code: $crate::types::c_int) -> Option<&'static str> {
127                match code {
128                    $($code => Some($msg),)+
129                    _ => None,
130                }
131            }
132
133            fn message(code: $crate::types::c_int, out: &mut $crate::MessageBuf) -> &str {
134                let msg = match Self::map_code(code) {
135                    Some(msg) => msg,
136                    None => $crate::utils::generic_map_error_code(code),
137                };
138
139                debug_assert!(msg.len() <= out.len());
140                unsafe {
141                    core::ptr::copy_nonoverlapping(msg.as_ptr(), out.as_mut_ptr() as *mut u8, msg.len());
142                    core::str::from_utf8_unchecked(
143                        core::slice::from_raw_parts(out.as_ptr() as *const u8, msg.len())
144                    )
145                }
146            }
147
148            ///Converts into error code
149            pub fn into_error_code(self) -> $crate::ErrorCode {
150                let _ = Self::_ASSERT;
151
152                static CATEGORY: $crate::Category = $crate::Category {
153                    name: core::stringify!($name),
154                    message: $name::message,
155                    equivalent,
156                    is_would_block
157                };
158
159                fn equivalent(code: $crate::types::c_int, other: &$crate::ErrorCode) -> bool {
160                    core::ptr::eq(&CATEGORY, other.category()) && code == other.raw_code()
161                }
162
163                fn is_would_block(_: $crate::types::c_int) -> bool {
164                    false
165                }
166
167                $crate::ErrorCode::new(self as _, &CATEGORY)
168            }
169        }
170    }
171}
172
173///Interface for error category
174///
175///It is implemented as pointers in order to avoid generics or overhead of fat pointers.
176///
177///## Custom implementation example
178///
179///```rust
180///use error_code::{ErrorCode, Category};
181///use error_code::types::c_int;
182///
183///use core::ptr;
184///
185///static MY_CATEGORY: Category = Category {
186///    name: "MyError",
187///    message,
188///    equivalent,
189///    is_would_block
190///};
191///
192///fn equivalent(code: c_int, other: &ErrorCode) -> bool {
193///    ptr::eq(&MY_CATEGORY, other.category()) && code == other.raw_code()
194///}
195///
196///fn is_would_block(_: c_int) -> bool {
197///    false
198///}
199///
200///fn message(code: c_int, out: &mut error_code::MessageBuf) -> &str {
201///    let msg = match code {
202///        0 => "Success",
203///        1 => "Bad",
204///        _ => "Whatever",
205///    };
206///
207///    debug_assert!(msg.len() <= out.len());
208///    unsafe {
209///        ptr::copy_nonoverlapping(msg.as_ptr(), out.as_mut_ptr() as *mut u8, msg.len())
210///    }
211///    msg
212///}
213///
214///#[inline(always)]
215///pub fn my_error(code: c_int) -> ErrorCode {
216///    ErrorCode::new(code, &MY_CATEGORY)
217///}
218///```
219pub struct Category {
220    ///Category name
221    pub name: &'static str,
222    ///Maps error code and writes descriptive error message accordingly.
223    ///
224    ///In case of insufficient buffer, prefer to truncate message or just don't write big ass message.
225    ///
226    ///In case of error, just write generic name.
227    ///
228    ///Returns formatted message as string.
229    pub message: fn(types::c_int, &mut MessageBuf) -> &str,
230    ///Checks whether error code is equivalent to another one.
231    ///
232    ///## Args:
233    ///
234    ///- Raw error code, belonging to this category
235    ///- Another error code being compared against this category.
236    ///
237    ///## Recommendation
238    ///
239    ///Generally error code is equal if it belongs to the same category (use `ptr::eq` to compare
240    ///pointers to `Category`) and raw error codes are equal.
241    pub equivalent: fn(types::c_int, &ErrorCode) -> bool,
242    ///Returns `true` if supplied error code indicates WouldBlock like error.
243    ///
244    ///This should `true` only for errors that indicate operation can be re-tried later.
245    pub is_would_block: fn(types::c_int) -> bool,
246}
247
248#[derive(Copy, Clone)]
249///Describes error code of particular category.
250pub struct ErrorCode {
251    code: types::c_int,
252    category: &'static Category
253}
254
255impl ErrorCode {
256    #[inline]
257    ///Initializes error code with provided category
258    pub const fn new(code: types::c_int, category: &'static Category) -> Self {
259        Self {
260            code,
261            category,
262        }
263    }
264
265    #[inline(always)]
266    ///Creates new POSIX error code.
267    pub fn new_posix(code: types::c_int) -> Self {
268        Self::new(code, &POSIX_CATEGORY)
269    }
270
271    #[inline(always)]
272    ///Creates new System error code.
273    pub fn new_system(code: types::c_int) -> Self {
274        Self::new(code, &SYSTEM_CATEGORY)
275    }
276
277    #[inline]
278    ///Gets last POSIX error
279    pub fn last_posix() -> Self {
280        Self::new_posix(posix::get_last_error())
281    }
282
283    #[inline]
284    ///Gets last System error
285    pub fn last_system() -> Self {
286        Self::new_system(system::get_last_error())
287    }
288
289    #[inline(always)]
290    ///Gets raw error code.
291    pub const fn raw_code(&self) -> types::c_int {
292        self.code
293    }
294
295    #[inline(always)]
296    ///Gets reference to underlying Category.
297    pub const fn category(&self) -> &'static Category {
298        self.category
299    }
300
301    #[inline(always)]
302    ///Returns `true` if underlying error indicates operation can or should be re-tried at later date.
303    pub fn is_would_block(&self) -> bool {
304        (self.category.is_would_block)(self.code)
305    }
306}
307
308impl PartialEq for ErrorCode {
309    #[inline]
310    fn eq(&self, other: &Self) -> bool {
311        (self.category.equivalent)(self.code, other)
312    }
313}
314
315impl Eq for ErrorCode {}
316
317impl hash::Hash for ErrorCode {
318    #[inline]
319    fn hash<H: hash::Hasher>(&self, state: &mut H) {
320        self.code.hash(state);
321    }
322}
323
324impl fmt::Debug for ErrorCode {
325    #[inline]
326    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
327        let mut out = [mem::MaybeUninit::uninit(); MESSAGE_BUF_SIZE];
328        let message = (self.category.message)(self.code, &mut out);
329        fmt.debug_struct(self.category.name).field("code", &self.code).field("message", &message).finish()
330    }
331}
332
333impl fmt::Display for ErrorCode {
334    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
335        let mut out = [mem::MaybeUninit::uninit(); MESSAGE_BUF_SIZE];
336        let message = (self.category.message)(self.code, &mut out);
337        fmt.write_fmt(format_args!("{}({}): {}", self.category.name, self.code, message))
338    }
339}
340
341#[cfg(feature = "std")]
342impl std::error::Error for ErrorCode {}
343
344#[cfg(feature = "std")]
345impl From<std::io::Error> for ErrorCode {
346    #[inline]
347    fn from(err: std::io::Error) -> Self {
348        match err.raw_os_error() {
349            Some(err) => Self::new_posix(err),
350            None => Self::new_posix(-1),
351        }
352    }
353}