1use std::error::Error as StdError;
10use std::fmt::{self, Debug, Display};
11use std::panic::{self, PanicHookInfo};
12use std::sync::Once;
13
14pub trait ResultExt<T, E> {
16 fn log_error(self, msg: &str) -> Self;
18 fn with_context<F, C>(self, f: F) -> Result<T, ContextError<E, C>>
20 where
21 F: FnOnce() -> C,
22 E: StdError + 'static,
23 C: Display + Debug + Send + Sync + 'static;
24}
25
26impl<T, E> ResultExt<T, E> for Result<T, E>
27where
28 E: StdError + 'static,
29{
30 fn log_error(self, msg: &str) -> Self {
31 if let Err(ref e) = self {
32 eprintln!("[ERROR] {}: {}", msg, e);
33 }
34 self
35 }
36
37 fn with_context<F, C>(self, f: F) -> Result<T, ContextError<E, C>>
38 where
39 F: FnOnce() -> C,
40 C: Display + Debug + Send + Sync + 'static,
41 {
42 self.map_err(|e| ContextError {
43 error: e,
44 context: f(),
45 })
46 }
47}
48
49pub trait OptionExt<T> {
51 fn log_none(self, msg: &str) -> Self;
53}
54
55impl<T> OptionExt<T> for Option<T> {
56 fn log_none(self, msg: &str) -> Self {
57 if self.is_none() {
58 eprintln!("[ERROR] {}: None value", msg);
59 }
60 self
61 }
62}
63
64#[derive(Debug)]
66pub struct ContextError<E, C> {
67 pub error: E,
68 pub context: C,
69}
70
71impl<E, C> Display for ContextError<E, C>
72where
73 E: Display,
74 C: Display,
75{
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "{} (context: {})", self.error, self.context)
78 }
79}
80
81impl<E, C> StdError for ContextError<E, C>
82where
83 E: StdError + 'static,
84 C: Display + Debug + Send + Sync + 'static,
85{
86 fn source(&self) -> Option<&(dyn StdError + 'static)> {
87 Some(&self.error)
88 }
89}
90
91pub fn error_chain(mut err: &(dyn StdError + 'static)) -> Vec<String> {
93 let mut chain = Vec::new();
94 loop {
95 chain.push(format!("{}", err));
96 match err.source() {
97 Some(source) => err = source,
98 None => break,
99 }
100 }
101 chain
102}
103
104static PANIC_HOOK_INIT: Once = Once::new();
105
106pub fn install_panic_hook() {
108 PANIC_HOOK_INIT.call_once(|| {
109 let default_hook = panic::take_hook();
110 panic::set_hook(Box::new(move |info: &PanicHookInfo| {
111 let location = info
112 .location()
113 .map(|l| l.to_string())
114 .unwrap_or_else(|| "unknown location".to_string());
115 let payload = if let Some(s) = info.payload().downcast_ref::<&str>() {
116 s.to_string()
117 } else if let Some(s) = info.payload().downcast_ref::<String>() {
118 s.clone()
119 } else {
120 "Box<Any>".to_string()
121 };
122 eprintln!("[PANIC] at {}: {}", location, payload);
123 default_hook(info);
124 }));
125 });
126}
127
128#[macro_export]
130macro_rules! source_location {
131 () => {
132 (file!(), line!(), column!())
133 };
134}
135
136#[macro_export]
138macro_rules! log_error_with_location {
139 ($err:expr) => {{
140 let (file, line, col) = $crate::source_location!();
141 eprintln!("[ERROR] at {}:{}:{}: {}", file, line, col, $err);
142 }};
143 ($err:expr, $msg:expr) => {{
144 let (file, line, col) = $crate::source_location!();
145 eprintln!("[ERROR] at {}:{}:{}: {}: {}", file, line, col, $msg, $err);
146 }};
147}