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}