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