Skip to main content

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