ovr_ruc/
err.rs

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