1static mut ERROR_HOOK: Option<fn(&str)> = None;
2
3pub fn register_error_hook(callback: fn(&str)) {
4 unsafe { ERROR_HOOK = Some(callback); }
5}
6
7#[macro_export]
8macro_rules! define_errors {
9 (
10 $(
11 $(#[$meta:meta])* $vis:vis enum $name:ident {
12 $( #[kind($kind:ident $(, $($tag:ident = $val:expr),* )?)]
13 $variant:ident $( { $($field:ident : $ftype:ty),* $(,)? } )?, )*
14 }
15 )*
16 ) => {
17 $(
18 $(#[$meta])* #[derive(Debug)]
19 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
20 $vis enum $name {
21 $( $variant $( { $($field : $ftype),* } )?, )*
22 }
23
24 impl $name {
25 $(
26 paste::paste! {
27 pub fn [<$variant:lower>]($($($field : $ftype),*)?) -> Self {
28 let instance = Self::$variant $( { $($field),* } )?;
29 #[allow(unsafe_code)]
30 unsafe {
31 if let Some(hook) = $crate::macros::ERROR_HOOK {
32 let _ = hook(instance.caption());
33 }
34 }
35 instance
36 }
37 }
38 )*
39
40 pub fn caption(&self) -> &'static str {
41 match self {
42 $( Self::$variant { .. } => {
43 define_errors!(@get_caption $kind $(, $($tag = $val),* )?)
44 } ),*
45 }
46 }
47
48 pub fn is_retryable(&self) -> bool {
49 match self {
50 $( Self::$variant { .. } => {
51 define_errors!(@get_tag retryable false $(, $($tag = $val),* )?)
52 } ),*
53 }
54 }
55
56 pub fn is_fatal(&self) -> bool {
57 match self {
58 $( Self::$variant { .. } => {
59 define_errors!(@get_tag fatal true $(, $($tag = $val),* )?)
60 } ),*
61 }
62 }
63
64 pub fn status_code(&self) -> u16 {
65 match self {
66 $( Self::$variant { .. } => {
67 define_errors!(@get_tag status 500 $(, $($tag = $val),* )?)
68 } ),*
69 }
70 }
71
72 pub fn exit_code(&self) -> i32 {
73 match self {
74 $( Self::$variant { .. } => {
75 define_errors!(@get_tag exit 1 $(, $($tag = $val),* )?)
76 } ),*
77 }
78 }
79 }
80
81 impl std::fmt::Display for $name {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 $( Self::$variant $( { $($field),* } )? => {
85 write!(f, "{}: ", self.caption())?;
86 write!(f, stringify!($variant))?;
87 $( $( write!(f, " | {} = {:?}", stringify!($field), $field)?; )* )?
88 Ok(())
89 } ),*
90 }
91 }
92 }
93
94 impl std::error::Error for $name {
95 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
96 match self {
97 $( Self::$variant $( { $($field),* } )? => {
98 define_errors!(@find_source $($field),*)
99 } ),*
100 }
101 }
102 }
103 )*
104 };
105
106 (@find_source $($field:ident),*) => {
107 {
108 let mut result = None;
109 $(
110 if result.is_none() {
111 let val = $field;
112 if let Some(err) = (val as &dyn std::any::Any).downcast_ref::<&(dyn std::error::Error + 'static)>() {
113 result = Some(*err);
114 }
115 }
116 )*
117 result
118 }
119 };
120
121 (@get_caption $kind:ident $(, caption = $caption:expr $(, $($rest:tt)*)? )?) => {
122 $crate::define_errors!(@unwrap_caption $kind $(, $caption)? )
123 };
124
125 (@unwrap_caption Config, $caption:expr) => { $caption };
126 (@unwrap_caption Filesystem, $caption:expr) => { $caption };
127 (@unwrap_caption $kind:ident) => { stringify!($kind) };
128
129 (@get_tag $target:ident, $default:expr $(, $($tag:ident = $val:expr),* )?) => {
130 {
131 let mut found = $default;
132 $( $( if stringify!($tag) == stringify!($target) { found = $val; })* )?
133 found
134 }
135 };
136}