ruc/err/
mod.rs

1//!
2//! # Error management
3//!
4//! All errors will be converted to RucError.
5//!
6//! ## Example
7//!
8//! ```rust
9//! use ruc::*;
10//!
11//! #[derive(Debug, Eq, PartialEq)]
12//! struct CustomErr(i32);
13//!
14//! fn will_panic() {
15//!     let l1 = || -> Result<()> { Err(eg!("The final error message!")) };
16//!     let l2 = || -> Result<()> { l1().c(d!()) };
17//!     let l3 = || -> Result<()> { l2().c(d!("A custom message!")) };
18//!     let l4 = || -> Result<()> { l3().c(d!("ERR_UNKNOWN")) };
19//!     let l5 = || -> Result<()> { l4().c(d!("{:?}", CustomErr(-1))) };
20//!
21//!     pnk!(l5());
22//! }
23//! ```
24//!
25
26mod macros;
27
28use core::{
29    any::{Any, TypeId},
30    fmt::{Debug, Display},
31};
32
33use std::{collections::HashSet, error::Error, sync::LazyLock, sync::Mutex};
34
35// avoid out-of-order printing
36static LOG_LK: Mutex<()> = Mutex::new(());
37
38/// `INFO` or `ERROR`, if mismatch, default to `INFO`
39pub static LOG_LEVEL: LazyLock<String> = LazyLock::new(|| {
40    if let Ok(l) = std::env::var("RUC_LOG_LEVEL")
41        && "ERROR" == l
42    {
43        return "ERROR".to_owned();
44    }
45    "INFO".to_owned()
46});
47
48/// Custom Result
49pub type Result<T> = core::result::Result<T, Box<dyn RucError>>;
50
51/// the major trait defination
52pub trait RucError: Display + Debug + Send {
53    /// type id of current error type
54    fn type_id(&self) -> TypeId;
55
56    /// type ids of errors of each level(from top to bottom).
57    fn type_ids(&self) -> Vec<TypeId> {
58        let mut res = Vec::new();
59        res.push(self.type_id());
60        while let Some(c) = self.cause() {
61            res.push(c.type_id());
62        }
63        res
64    }
65
66    /// get the type of the lowest(bottom) error
67    fn lowest_type_id(&self) -> TypeId {
68        *self.type_ids().last().unwrap()
69    }
70
71    /// check the type of the lowest error
72    fn lowest_is_type(&self, e: &dyn Any) -> bool {
73        self.lowest_type_id() == e.type_id()
74    }
75
76    /// check if an error exists in the error chain
77    fn contains_type(&self, e: &dyn Any) -> bool {
78        self.type_ids().contains(&e.type_id())
79    }
80
81    /// compare two object
82    fn msg_eq(&self, another: &dyn RucError) -> bool {
83        self.get_lowest_msg() == another.get_lowest_msg()
84    }
85
86    /// check if any node from the error_chain matches the given error
87    fn msg_has_overloop(&self, another: &dyn RucError) -> bool {
88        let mut b;
89
90        let mut self_list = HashSet::new();
91        self_list.insert(self.get_top_msg());
92        b = self.cause();
93        while let Some(next) = b {
94            self_list.insert(next.get_top_msg());
95            b = next.cause();
96        }
97
98        let mut target_list = HashSet::new();
99        target_list.insert(another.get_top_msg());
100        b = another.cause();
101        while let Some(next) = b {
102            target_list.insert(next.get_top_msg());
103            b = next.cause();
104        }
105
106        !self_list.is_disjoint(&target_list)
107    }
108
109    /// convert the error of current level to string
110    fn get_top_msg(&self) -> String;
111
112    /// convert the error of lowest level to string
113    fn get_lowest_msg(&self) -> String;
114
115    /// Get the original error object,
116    /// used to match its original type by `Any`.
117    fn get_lowest_err(&self) -> &dyn RucError;
118
119    /// "error msg" + "debug info"
120    fn get_top_msg_with_dbginfo(&self) -> String;
121
122    /// point to a error which caused current error
123    fn cause(&self) -> Option<&dyn RucError> {
124        None
125    }
126
127    /// generate the final error msg
128    fn stringify_chain(&self, prefix: Option<&str>) -> String {
129        let mut res =
130            format!("{}{}: ", delimiter(), prefix.unwrap_or("ERROR"));
131        res.push_str(&self.get_top_msg_with_dbginfo());
132        let mut e = self.cause();
133        let mut indent_num = 0;
134        while let Some(c) = e {
135            let mut prefix = delimiter().to_owned();
136            (0..indent_num).for_each(|_| {
137                prefix.push_str(indent());
138            });
139            res.push_str(&prefix);
140            res.push_str("Caused By: ");
141            res.push_str(&c.get_top_msg_with_dbginfo().replace('\n', &prefix));
142            indent_num += 1;
143            e = c.cause();
144        }
145        res
146    }
147
148    /// Panic after printing `error_chain`
149    #[inline(always)]
150    fn print_die(&self) -> ! {
151        self.print(None);
152        panic!();
153    }
154
155    /// Generate the log string
156    #[inline(always)]
157    fn generate_log(&self, prefix: Option<&str>) -> String {
158        self.generate_log_custom(prefix)
159    }
160
161    /// Generate the log string with custom mode
162    fn generate_log_custom(&self, prefix: Option<&str>) -> String {
163        #[cfg(not(feature = "ansi"))]
164        #[inline(always)]
165        fn generate_log_header(ns: String, pid: u32) -> String {
166            format!(
167                "\n\x1b[31;01m# {time} [pid: {pid}] [pidns: {ns}]\x1b[00m",
168                time = crate::datetime!(),
169                pid = pid,
170                ns = ns,
171            )
172        }
173
174        #[cfg(feature = "ansi")]
175        #[inline(always)]
176        fn generate_log_header(ns: String, pid: u32) -> String {
177            format!(
178                "\n# {time} [pid: {pid}] [pidns: {ns}]",
179                time = crate::datetime!(),
180                pid = pid,
181                ns = ns,
182            )
183        }
184
185        let pid = std::process::id();
186
187        // can not call `p` in the inner,
188        // or will cause a infinite loop
189        let ns = get_pidns(pid).unwrap();
190
191        let mut res = generate_log_header(ns, pid);
192        res.push_str(&self.stringify_chain(prefix));
193        res
194    }
195
196    /// Print log
197    #[inline(always)]
198    fn print(&self, prefix: Option<&str>) {
199        if LOG_LK.lock().is_ok() {
200            eprintln!("{}", self.generate_log(prefix));
201        }
202    }
203}
204
205/// Convert all `Result` to this
206pub trait RucResult<T, E: Debug + Display + Send> {
207    /// shorter alias for 'chain_error'
208    fn c(self, msg: SimpleMsg<E>) -> Result<T>;
209}
210
211impl<T, E: Debug + Display + Send> RucResult<T, E> for Result<T> {
212    #[inline(always)]
213    fn c(self, msg: SimpleMsg<E>) -> Result<T> {
214        self.map_err(|e| SimpleError::new(msg, Some(e)).into())
215    }
216}
217
218impl<T, E: Debug + Display + Send> RucResult<T, E> for Option<T> {
219    #[inline(always)]
220    fn c(self, msg: SimpleMsg<E>) -> Result<T> {
221        self.ok_or_else(|| SimpleError::new(msg, None).into())
222    }
223}
224
225impl<T, E: Debug + Display + Send, ERR: Error> RucResult<T, E>
226    for core::result::Result<T, ERR>
227{
228    #[inline(always)]
229    fn c(self, msg: SimpleMsg<E>) -> Result<T> {
230        self.map_err(|e| {
231            let inner =
232                SimpleMsg::new(e.to_string(), &msg.file, msg.line, msg.column);
233            SimpleError::new(
234                msg,
235                Some(Box::new(SimpleError::new(inner, None))),
236            )
237            .into()
238        })
239    }
240}
241
242/// A pre-impled Error
243#[derive(Debug)]
244pub struct SimpleError<E: Debug + Display + Send + 'static> {
245    msg: SimpleMsg<E>,
246    cause: Option<Box<dyn RucError>>,
247}
248
249impl<E: Debug + Display + Send + 'static> SimpleError<E> {
250    #[allow(missing_docs)]
251    #[inline(always)]
252    pub fn new(msg: SimpleMsg<E>, cause: Option<Box<dyn RucError>>) -> Self {
253        SimpleError { msg, cause }
254    }
255}
256
257impl<E: Debug + Display + Send + 'static> Display for SimpleError<E> {
258    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
259        write!(f, "{}", self.generate_log(None))
260    }
261}
262
263impl<E: Debug + Display + Send + 'static> From<SimpleError<E>>
264    for Box<dyn RucError>
265{
266    fn from(e: SimpleError<E>) -> Box<dyn RucError> {
267        Box::new(e)
268    }
269}
270
271impl<E: Debug + Display + Send + 'static> RucError for SimpleError<E> {
272    fn type_id(&self) -> TypeId {
273        TypeId::of::<E>()
274    }
275
276    /// get the top-level error message
277    #[inline(always)]
278    fn get_top_msg(&self) -> String {
279        self.msg.err.to_string()
280    }
281
282    /// get the final(lowest) error message
283    #[inline(always)]
284    fn get_lowest_msg(&self) -> String {
285        if let Some(next) = self.cause.as_ref() {
286            next.get_lowest_msg()
287        } else {
288            self.msg.err.to_string()
289        }
290    }
291
292    fn get_lowest_err(&self) -> &dyn RucError {
293        if let Some(next) = self.cause.as_ref() {
294            next.get_lowest_err()
295        } else {
296            self
297        }
298    }
299
300    #[inline(always)]
301    fn get_top_msg_with_dbginfo(&self) -> String {
302        self.msg.to_string()
303    }
304
305    #[inline(always)]
306    fn cause(&self) -> Option<&dyn RucError> {
307        self.cause.as_deref()
308    }
309}
310
311/// error + <file + line + column>
312#[derive(Debug)]
313pub struct SimpleMsg<E: Debug + Display + Send + 'static> {
314    /// actual error
315    pub err: E,
316    /// file path
317    pub file: String,
318    /// line number
319    pub line: u32,
320    /// column number
321    pub column: u32,
322}
323
324impl<E: Debug + Display + Send + 'static> SimpleMsg<E> {
325    /// create new error
326    #[inline(always)]
327    pub fn new(err: E, file: &str, line: u32, column: u32) -> Self {
328        SimpleMsg {
329            err,
330            file: file.to_owned(),
331            line,
332            column,
333        }
334    }
335}
336
337impl<E: Debug + Display + Send + 'static> Display for SimpleMsg<E> {
338    #[cfg(feature = "ansi")]
339    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
340        write!(
341            f,
342            "{0}{4}{5}file: {1}{4}{5}line: {2}{4}{6}column: {3}",
343            self.err,
344            self.file,
345            self.line,
346            self.column,
347            delimiter(),
348            pretty()[0],
349            pretty()[1]
350        )
351    }
352
353    #[cfg(not(feature = "ansi"))]
354    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
355        write!(
356            f,
357            "\x1b[01m{0}\x1b[00m{4}{5}\x1b[01mfile:\x1b[00m {1}{4}{5}\x1b[01mline:\x1b[00m {2}{4}{6}\x1b[01mcolumn:\x1b[00m {3}",
358            self.err,
359            self.file,
360            self.line,
361            self.column,
362            delimiter(),
363            pretty()[0],
364            pretty()[1]
365        )
366    }
367}
368
369impl<E: Debug + Display + Send + 'static> From<SimpleMsg<E>>
370    for Box<dyn RucError>
371{
372    fn from(m: SimpleMsg<E>) -> Self {
373        SimpleError::new(m, None).into()
374    }
375}
376
377#[inline(always)]
378#[cfg(target_os = "linux")]
379fn get_pidns(pid: u32) -> Result<String> {
380    std::fs::read_link(format!("/proc/{pid}/ns/pid"))
381        .c(crate::d!())
382        .map(|p| {
383            p.to_string_lossy()
384                .trim_start_matches("pid:[")
385                .trim_end_matches(']')
386                .to_owned()
387        })
388}
389
390#[inline(always)]
391#[cfg(not(target_os = "linux"))]
392#[allow(clippy::unnecessary_wraps)]
393fn get_pidns(_pid: u32) -> Result<String> {
394    Ok("NULL".to_owned())
395}
396
397#[cfg(not(feature = "compact"))]
398const fn delimiter() -> &'static str {
399    "\n"
400}
401
402#[cfg(feature = "compact")]
403const fn delimiter() -> &'static str {
404    " 》"
405}
406
407#[cfg(not(feature = "compact"))]
408const fn indent() -> &'static str {
409    "    "
410}
411
412#[cfg(feature = "compact")]
413const fn indent() -> &'static str {
414    ""
415}
416
417#[cfg(all(not(feature = "compact"), feature = "ansi"))]
418const fn pretty() -> [&'static str; 2] {
419    ["|--", "`--"]
420}
421
422#[cfg(all(not(feature = "compact"), not(feature = "ansi")))]
423const fn pretty() -> [&'static str; 2] {
424    ["├──", "└──"]
425}
426
427#[cfg(feature = "compact")]
428const fn pretty() -> [&'static str; 2] {
429    ["", ""]
430}
431
432#[cfg(test)]
433mod test {
434    use super::*;
435    use std::process;
436
437    #[test]
438    fn t_get_pidns() {
439        let ns_name = crate::pnk!(get_pidns(process::id()));
440        assert!(1 < ns_name.len());
441    }
442
443    #[test]
444    fn t_error_chain() {
445        let res: Result<i32> = Err(SimpleError::new(
446            SimpleMsg::new("***", "/tmp/xx.rs", 9, 90),
447            None,
448        )
449        .into());
450        println!(
451            "{}",
452            res.c(SimpleMsg::new("cat", "/tmp/xx.rs", 1, 10))
453                .c(SimpleMsg::new("dog", "/tmp/xx.rs", 2, 20))
454                .c(SimpleMsg::new("pig", "/tmp/xx.rs", 3, 30))
455                .unwrap_err()
456                .stringify_chain(None)
457        );
458
459        let e1: Box<dyn RucError> =
460            SimpleError::new(SimpleMsg::new("***", "/tmp/xx.rs", 9, 90), None)
461                .into();
462        let e2: Box<dyn RucError> =
463            SimpleError::new(SimpleMsg::new("***", "/tmp/xx.rs", 9, 90), None)
464                .into();
465
466        assert!(e1.msg_eq(e2.as_ref()));
467        assert!(e1.lowest_is_type(&""));
468        assert!(e2.lowest_is_type(&""));
469        assert_eq!(e2.lowest_type_id(), TypeId::of::<&str>());
470    }
471}