1mod 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
35static LOG_LK: Mutex<()> = Mutex::new(());
37
38pub 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
48pub type Result<T> = core::result::Result<T, Box<dyn RucError>>;
50
51pub trait RucError: Display + Debug + Send {
53 fn type_id(&self) -> TypeId;
55
56 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 fn lowest_type_id(&self) -> TypeId {
68 *self.type_ids().last().unwrap()
69 }
70
71 fn lowest_is_type(&self, e: &dyn Any) -> bool {
73 self.lowest_type_id() == e.type_id()
74 }
75
76 fn contains_type(&self, e: &dyn Any) -> bool {
78 self.type_ids().contains(&e.type_id())
79 }
80
81 fn msg_eq(&self, another: &dyn RucError) -> bool {
83 self.get_lowest_msg() == another.get_lowest_msg()
84 }
85
86 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 fn get_top_msg(&self) -> String;
111
112 fn get_lowest_msg(&self) -> String;
114
115 fn get_lowest_err(&self) -> &dyn RucError;
118
119 fn get_top_msg_with_dbginfo(&self) -> String;
121
122 fn cause(&self) -> Option<&dyn RucError> {
124 None
125 }
126
127 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 #[inline(always)]
150 fn print_die(&self) -> ! {
151 self.print(None);
152 panic!();
153 }
154
155 #[inline(always)]
157 fn generate_log(&self, prefix: Option<&str>) -> String {
158 self.generate_log_custom(prefix)
159 }
160
161 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 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 #[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
205pub trait RucResult<T, E: Debug + Display + Send> {
207 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#[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 #[inline(always)]
278 fn get_top_msg(&self) -> String {
279 self.msg.err.to_string()
280 }
281
282 #[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#[derive(Debug)]
313pub struct SimpleMsg<E: Debug + Display + Send + 'static> {
314 pub err: E,
316 pub file: String,
318 pub line: u32,
320 pub column: u32,
322}
323
324impl<E: Debug + Display + Send + 'static> SimpleMsg<E> {
325 #[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}