1use 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
16static LOG_LK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
18
19pub 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
29pub type Result<T> = std::result::Result<T, Box<dyn RucError>>;
31
32pub trait RucError: Display + Debug + Send {
34 fn type_id(&self) -> TypeId;
36
37 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 fn lowest_type_id(&self) -> TypeId {
48 *self.type_ids().last().unwrap()
49 }
50
51 fn lowest_is_type(&self, e: &dyn Any) -> bool {
53 self.lowest_type_id() == e.type_id()
54 }
55
56 fn contains_type(&self, e: &dyn Any) -> bool {
58 self.type_ids().contains(&e.type_id())
59 }
60
61 fn msg_eq(&self, another: &dyn RucError) -> bool {
63 self.get_lowest_msg() == another.get_lowest_msg()
64 }
65
66 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 fn get_top_msg(&self) -> String;
91
92 fn get_lowest_msg(&self) -> String;
94
95 fn get_lowest_err(&self) -> &dyn RucError;
98
99 fn get_top_msg_with_dbginfo(&self) -> String;
101
102 fn cause(&self) -> Option<&dyn RucError> {
104 None
105 }
106
107 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 #[inline(always)]
130 fn print_die(&self) -> ! {
131 self.print(None);
132 panic!();
133 }
134
135 #[inline(always)]
137 fn print_die_debug(&self) -> ! {
138 self.print_debug();
139 panic!();
140 }
141
142 #[inline(always)]
144 fn generate_log(&self, prefix: Option<&str>) -> String {
145 self.generate_log_custom(false, prefix)
146 }
147
148 #[inline(always)]
150 fn generate_log_debug(&self) -> String {
151 self.generate_log_custom(true, None)
152 }
153
154 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 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 #[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 #[inline(always)]
213 fn print_debug(&self) {
214 if LOG_LK.lock().is_ok() {
215 eprintln!("{}", self.generate_log_debug());
216 }
217 }
218}
219
220pub trait RucResult<T, E: Debug + Display + Send> {
222 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#[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 #[inline(always)]
293 fn get_top_msg(&self) -> String {
294 self.msg.err.to_string()
295 }
296
297 #[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#[derive(Debug)]
328pub struct SimpleMsg<E: Debug + Display + Send + 'static> {
329 pub err: E,
331 pub file: String,
333 pub line: u32,
335 pub column: u32,
337}
338
339impl<E: Debug + Display + Send + 'static> SimpleMsg<E> {
340 #[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}