error_forge/
macros.rs

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}