hierr/
lib.rs

1//! 包装OS的错误码, 统一错误码的查询和获取接口
2//! 1. fn errno() -> i32;
3//! 2. fn set_errno(i32);
4//! 3. fn errmsg(i32, &mut [u8]) -> &str;
5//!
6//! 封装i32为Error
7//!
8//! # Example
9//! ```rust
10//! use hierr;
11//!
12//! hierr::set_errno(100);
13//! let err = hierr::Error::last();
14//!
15//! println!("{}", err);
16//!
17//! let mut buf = [0_u8; 64];
18//! println!("{}: {}",  hierr::errno(), hierr::errmsg(hierr::errno(), &mut buf[..]));
19//!
20//! assert_eq!(err, 100.into());
21//! assert_eq!(hierr::errno(), 100);
22//!
23//! ```
24
25#![no_std]
26
27use core::fmt;
28use core::result;
29use core::str;
30
31pub type Result<T> = result::Result<T, Error>;
32
33/// 错误码有两种使用场景:
34/// 1. 需要区分具体错误码: 比如底层`target`提供接口,需要调用者显示区分`target`
35/// 2. 无需区分具体错误码: 一般有高层次,粗粒度的容错处理机制,错误码用于日志输出.
36/// 对于后一种场景,调用者决定日志输出方式,因此接口的返回值也多使用`Result<T, Error>`.
37/// 这里就会存在`Error`具体取值的构造尽可能和`Target`无关,因此为此场景定义了几个常用业务下需要使用到的错误码:
38/// 1. Error::inval
39/// 1. Error::noent
40/// 1. Error::perm
41/// 1. Error::nomem
42/// 1. Error::busy
43/// 1. Error::timedout
44/// 1. Error::general
45/// 借用`Target`定义的错误码来表达业务层的常见错误.
46/// 因为此场景并不鼓励基于具体错误码进行容错处理,因此并不提供对应的`is_***`接口.
47#[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
48#[repr(transparent)]
49pub struct Error {
50    pub errno: i32,
51}
52
53impl Error {
54    pub const fn new(errno: i32) -> Self {
55        Self { errno }
56    }
57    pub fn last() -> Self {
58        Self { errno: errno() }
59    }
60
61    /// 表示输入错误. 适用于业务层主动返回错误码,避免使用具体数字导致跨平台兼容问题.
62    pub const fn inval() -> Self {
63        #[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
64        {
65            Self { errno: EINVAL }
66        }
67        #[cfg(all(target_os = "windows", not(target_env = "gnu")))]
68        {
69            Self {
70                errno: WSA_INVALID_PARAMETER,
71            }
72        }
73    }
74
75    /// 表示资源不存在. 适用于业务层主动返回错误码,避免使用具体数字导致跨平台兼容问题.
76    pub const fn noent() -> Self {
77        #[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
78        {
79            Self { errno: ENOENT }
80        }
81        #[cfg(all(target_os = "windows", not(target_env = "gnu")))]
82        {
83            // ERROR_FILE_NOT_FOUND
84            Self { errno: 1 }
85        }
86    }
87
88    /// 表示权限不足. 适用于业务层主动返回错误码,避免使用具体数字导致跨平台兼容问题.
89    pub const fn perm() -> Self {
90        #[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
91        {
92            Self { errno: EPERM }
93        }
94        #[cfg(all(target_os = "windows", not(target_env = "gnu")))]
95        {
96            // ERROR_ACCESS_DENIED
97            Self { errno: 5 }
98        }
99    }
100
101    /// 表示内存不足. 适用于业务层主动返回错误码,避免使用具体数字导致跨平台兼容问题.
102    pub const fn nomem() -> Self {
103        #[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
104        {
105            Self { errno: ENOMEM }
106        }
107        #[cfg(all(target_os = "windows", not(target_env = "gnu")))]
108        {
109            Self {
110                errno: WSA_NOT_ENOUGH_MEMORY,
111            }
112        }
113    }
114
115    /// 表示繁忙(内部资源受限)返回. 适用于业务层主动返回错误码,避免使用具体数字导致跨平台兼容问题.
116    pub const fn busy() -> Self {
117        #[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
118        {
119            Self { errno: EBUSY }
120        }
121        #[cfg(all(target_os = "windows", not(target_env = "gnu")))]
122        {
123            // ERROR_BUSY
124            Self { errno: 170 }
125        }
126    }
127
128    /// 表示超时返回. 适用于业务层主动返回错误码,避免使用具体数字导致跨平台兼容问题.
129    pub const fn timedout() -> Self {
130        #[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
131        {
132            Self { errno: ETIMEDOUT }
133        }
134        #[cfg(all(target_os = "windows", not(target_env = "gnu")))]
135        {
136            Self {
137                errno: WSAETIMEDOUT,
138            }
139        }
140    }
141
142    /// 表示难以归类的通用错误. 适用于业务层主动返回错误码,避免使用具体数字导致跨平台兼容问题.
143    pub const fn general() -> Self {
144        Self { errno: -1 }
145    }
146}
147
148impl Default for Error {
149    fn default() -> Self {
150        Error::general()
151    }
152}
153
154impl From<i32> for Error {
155    fn from(i32: i32) -> Self {
156        Self::new(i32)
157    }
158}
159
160impl From<Error> for Result<()> {
161    fn from(err: Error) -> Self {
162        if err.errno == 0 {
163            Ok(())
164        } else {
165            Err(err)
166        }
167    }
168}
169
170const ERRMSG_MAX_SIZE: usize = 64;
171impl fmt::Display for Error {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {
173        let mut msg = [0_u8; ERRMSG_MAX_SIZE];
174        write!(f, "{}: {}", self.errno, errmsg(self.errno, &mut msg[..]))
175    }
176}
177
178impl fmt::Debug for Error {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {
180        let mut msg = [0_u8; ERRMSG_MAX_SIZE];
181        write!(f, "{}: {}", self.errno, errmsg(self.errno, &mut msg[..]))
182    }
183}
184
185#[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
186fn format_message_utf8<'a>(input: &[u8], output: &'a mut [u8]) -> &'a str {
187    let Ok(s) = str::from_utf8(input) else {
188        return get_message(output, 0);
189    };
190    if input.len() <= output.len() {
191        output[..input.len()].copy_from_slice(input);
192        return str::from_utf8(&output[..input.len()]).unwrap();
193    }
194    return truncate_message(s, output);
195}
196
197#[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
198fn truncate_message<'a>(input: &str, output: &'a mut [u8]) -> &'a str {
199    let mut pos = 0;
200    for c in input.chars() {
201        if !put_message(output, &mut pos, c) {
202            break;
203        }
204    }
205    get_message(output, pos)
206}
207
208fn put_message(output: &mut [u8], pos: &mut usize, c: char) -> bool {
209    if *pos + c.len_utf8() <= output.len() {
210        c.encode_utf8(&mut output[*pos..]);
211        *pos += c.len_utf8();
212        return true;
213    }
214    false
215}
216
217fn get_message(output: &[u8], len: usize) -> &str {
218    if len > 0 {
219        str::from_utf8(&output[..len]).unwrap()
220    } else {
221        "Unknown error"
222    }
223}
224
225#[cfg(all(target_os = "windows", not(target_env = "gnu")))]
226fn format_message_utf16<'a>(input: &[u16], output: &'a mut [u8]) -> &'a str {
227    let mut pos = 0;
228    char::decode_utf16(input.iter().copied()).any(|c| {
229        let Ok(c) = c else {
230            pos = 0;
231            return true;
232        };
233        !put_message(output, &mut pos, c)
234    });
235    get_message(output, pos)
236}
237
238#[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
239mod unix;
240#[cfg(any(unix, all(target_os = "windows", target_env = "gnu")))]
241pub use unix::*;
242
243#[cfg(all(target_os = "windows", not(target_env = "gnu")))]
244mod windows;
245#[cfg(all(target_os = "windows", not(target_env = "gnu")))]
246pub use windows::*;
247
248#[cfg(test)]
249mod test {
250    use super::*;
251    extern crate std;
252    use std::println;
253
254    #[test]
255    fn test_errno() {
256        set_errno(100);
257        assert_eq!(errno(), 100);
258    }
259
260    #[test]
261    fn test_print() {
262        for n in 0..=102 {
263            let err = Error::new(n);
264            println!("[{}]\n[{:?}]\n", err, err);
265        }
266
267        set_errno(4);
268        println!("5: {}", errmsg(5, &mut [0_u8; 7]));
269    }
270}