1#[derive(Clone, Copy, PartialEq, Eq, Debug)]
3pub enum ErrorLevel {
4 Debug,
6 Info,
8 Warning,
10 Error,
12 Critical,
14}
15
16pub struct ErrorContext<'a> {
18 pub caption: &'a str,
20 pub kind: &'a str,
22 pub level: ErrorLevel,
24 pub is_fatal: bool,
26 pub is_retryable: bool,
28}
29
30use std::sync::OnceLock;
31
32static ERROR_HOOK: OnceLock<fn(ErrorContext)> = OnceLock::new();
34
35pub fn register_error_hook(callback: fn(ErrorContext)) {
60 let _ = ERROR_HOOK.set(callback);
61}
62
63#[doc(hidden)]
65pub fn call_error_hook(caption: &str, kind: &str, is_fatal: bool, is_retryable: bool) {
66 if let Some(hook) = ERROR_HOOK.get() {
67 let level = if is_fatal {
69 ErrorLevel::Critical
70 } else if !is_retryable {
71 ErrorLevel::Error
72 } else if kind == "Warning" {
73 ErrorLevel::Warning
74 } else if kind == "Debug" {
75 ErrorLevel::Debug
76 } else {
77 ErrorLevel::Info
78 };
79
80 hook(ErrorContext {
81 caption,
82 kind,
83 level,
84 is_fatal,
85 is_retryable,
86 });
87 }
88}
89
90#[macro_export]
91macro_rules! define_errors {
92 (
93 $(
94 $(#[$meta:meta])* $vis:vis enum $name:ident {
95 $(
96 $(#[error(display = $display:literal $(, $($display_param:ident),* )?)])?
97 #[kind($kind:ident $(, $($tag:ident = $val:expr),* )?)]
98 $variant:ident $( { $($field:ident : $ftype:ty),* $(,)? } )?, )*
99 }
100 )*
101 ) => {
102 $(
103 $(#[$meta])* #[derive(Debug)]
104 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
105 $vis enum $name {
106 $( $variant $( { $($field : $ftype),* } )?, )*
107 }
108
109 impl $name {
110 $(
111 paste::paste! {
112 pub fn [<$variant:lower>]($($($field : $ftype),*)?) -> Self {
113 let instance = Self::$variant $( { $($field),* } )?;
114 $crate::macros::call_error_hook(
116 instance.caption(),
117 instance.kind(),
118 instance.is_fatal(),
119 instance.is_retryable()
120 );
121 instance
122 }
123 }
124 )*
125
126 pub fn caption(&self) -> &'static str {
127 match self {
128 $( Self::$variant { .. } => {
129 define_errors!(@get_caption $kind $(, $($tag = $val),* )?)
130 } ),*
131 }
132 }
133
134 pub fn kind(&self) -> &'static str {
135 match self {
136 $( Self::$variant { .. } => {
137 stringify!($kind)
138 } ),*
139 }
140 }
141
142 pub fn is_retryable(&self) -> bool {
143 match self {
144 $( Self::$variant { .. } => {
145 define_errors!(@get_tag retryable false $(, $($tag = $val),* )?)
146 } ),*
147 }
148 }
149
150 pub fn is_fatal(&self) -> bool {
151 match self {
152 $( Self::$variant { .. } => {
153 define_errors!(@get_tag fatal true $(, $($tag = $val),* )?)
154 } ),*
155 }
156 }
157
158 pub fn status_code(&self) -> u16 {
159 match self {
160 $( Self::$variant { .. } => {
161 define_errors!(@get_tag status 500 $(, $($tag = $val),* )?)
162 } ),*
163 }
164 }
165
166 pub fn exit_code(&self) -> i32 {
167 match self {
168 $( Self::$variant { .. } => {
169 define_errors!(@get_tag exit 1 $(, $($tag = $val),* )?)
170 } ),*
171 }
172 }
173 }
174
175 impl std::fmt::Display for $name {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 match self {
178 $( Self::$variant $( { $($field),* } )? => {
179 $(
180 #[allow(unused_variables)]
181 if let Some(display) = define_errors!(@format_display $display $(, $($display_param),*)?) {
182 return write!(f, "{}", display);
183 }
184 )?
185 write!(f, "{}: ", self.caption())?;
187 write!(f, stringify!($variant))?;
188 $( $(
190 write!(f, " | {} = ", stringify!($field))?
191 ;
192 match stringify!($field) {
193 "source" => write!(f, "{}", $field)?,
194 _ => write!(f, "{:?}", $field)?,
195 }
196 ; )* )?
197 Ok(())
198 } ),*
199 }
200 }
201 }
202
203 impl std::error::Error for $name {
204 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
205 match self {
206 $( Self::$variant $( { $($field),* } )? => {
207 define_errors!(@find_source $($field),*)
208 } ),*
209 }
210 }
211 }
212 )*
213 };
214
215 (@find_source) => {
216 None
217 };
218
219 (@find_source $field:ident) => {
220 {
221 let val = $field;
222 if let Some(err) = (val as &dyn std::any::Any).downcast_ref::<&(dyn std::error::Error + 'static)>() {
223 Some(*err)
224 } else {
225 None
226 }
227 }
228 };
229
230 (@find_source $field:ident, $($rest:ident),+) => {
231 {
232 let val = $field;
233 if let Some(err) = (val as &dyn std::any::Any).downcast_ref::<&(dyn std::error::Error + 'static)>() {
234 Some(*err)
235 } else {
236 define_errors!(@find_source $($rest),+)
237 }
238 }
239 };
240
241 (@get_caption $kind:ident $(, caption = $caption:expr $(, $($rest:tt)*)? )?) => {
242 $crate::define_errors!(@unwrap_caption $kind $(, $caption)? )
243 };
244
245 (@unwrap_caption Config, $caption:expr) => { $caption };
246 (@unwrap_caption Filesystem, $caption:expr) => { $caption };
247 (@unwrap_caption $kind:ident) => { stringify!($kind) };
248
249 (@get_tag $target:ident, $default:expr $(, $($tag:ident = $val:expr),* )?) => {
250 {
251 let mut found = $default;
252 $( $( if stringify!($tag) == stringify!($target) { found = $val; })* )?
253 found
254 }
255 };
256
257 (@format_display $display:literal $(, $($param:ident),*)?) => {
258 {
259 $(
261 Some(format!($display, $($param = $param),*))
262 )?
263 $(
265 Some($display.to_string())
266 )?
267 }
268 };
269
270 (@format_display_field $field:ident) => {
272 $field
273 };
274
275 (@format_display_field $field:ident . $($rest:ident).+) => {
276 $field$(.$rest)+
277 };
278}