error_forge/
group_macro.rs

1/// Provides macros for grouping errors and generating automatic conversions
2// StdError used in generated code from macros
3#[allow(unused_imports)]
4use std::error::Error as StdError;
5
6/// Macro for composing multi-error enums with automatic `From<OtherError>` conversions.
7/// 
8/// This macro allows you to create a parent error type that can wrap multiple other error types,
9/// automatically implementing From conversions for each of them.
10/// 
11/// # Example
12/// 
13/// ```ignore
14/// use error_forge::{group, AppError};
15/// use std::io;
16/// 
17/// // Define a custom error type for a specific module
18/// #[derive(Debug, thiserror::Error)]
19/// pub enum DatabaseError {
20///     #[error("Connection failed: {0}")]
21///     ConnectionFailed(String),
22///     
23///     #[error("Query failed: {0}")]
24///     QueryFailed(String),
25/// }
26/// 
27/// // Group multiple error types into a parent error
28/// group! {
29///     #[derive(Debug)]
30///     pub enum ServiceError {
31///         // Include the AppError
32///         App(AppError),
33///         
34///         // Include io::Error
35///         Io(io::Error),
36///         
37///         // Include the custom DatabaseError
38///         Database(DatabaseError),
39///     }
40/// }
41/// ```
42#[macro_export]
43macro_rules! group {
44    // First pattern - simple wrapped errors without extra variants
45    (
46        $(#[$meta:meta])*
47        $vis:vis enum $name:ident {
48            $(
49                $(#[$vmeta:meta])*
50                $variant:ident($source_type:ty)
51            ),* $(,)?
52        }
53    ) => {
54        group!(@with_impl
55            $(#[$meta])* $vis enum $name {
56                $(
57                    $(#[$vmeta])*
58                    $variant($source_type),
59                )*
60            }
61            $(
62                $variant $source_type
63            )*
64        )
65    };
66    
67    // Internal implementation with all necessary impls
68    (@with_impl
69        $(#[$meta:meta])* $vis:vis enum $name:ident {
70            $(
71                $(#[$vmeta:meta])*
72                $variant:ident($source_type:ty),
73            )*
74            $(
75                $(#[$vmeta_extra:meta])*
76                $variant_extra:ident $({$($field:ident: $field_type:ty),*})?,
77            )*
78        }
79        $($impl_variant:ident $impl_type:ty)*
80    ) => {
81        $(#[$meta])*
82        $vis enum $name {
83            $(
84                $(#[$vmeta])*
85                $variant($source_type),
86            )*
87            $(
88                $(#[$vmeta_extra])*
89                $variant_extra $({$($field: $field_type),*})?,
90            )*
91        }
92
93        impl std::fmt::Display for $name {
94            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95                match self {
96                    $(
97                        Self::$variant(source) => write!(f, "{}", source),
98                    )*
99                    $(
100                        Self::$variant_extra $({$($field),*})? => {
101                            let error_name = stringify!($variant_extra);
102                            write!(f, "{} error", error_name)
103                        }
104                    )*
105                }
106            }
107        }
108
109        impl std::error::Error for $name {
110            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111                match self {
112                    $(
113                        Self::$variant(source) => Some(source as &(dyn std::error::Error + 'static)),
114                    )*
115                    _ => None,
116                }
117            }
118        }
119
120        $(
121            impl From<$source_type> for $name {
122                fn from(source: $source_type) -> Self {
123                    Self::$variant(source)
124                }
125            }
126        )*
127
128        // Implement ForgeError trait for our grouped error enum
129        impl $crate::error::ForgeError for $name {
130            fn kind(&self) -> &'static str {
131                match self {
132                    $(
133                        Self::$variant(source) => {
134                            if let Some(forge_err) = (source as &dyn std::any::Any)
135                                .downcast_ref::<&(dyn $crate::error::ForgeError)>() 
136                            {
137                                return forge_err.kind();
138                            }
139                            stringify!($variant)
140                        },
141                    )*
142                    $(
143                        Self::$variant_extra $({$($field),*})? => stringify!($variant_extra),
144                    )*
145                }
146            }
147            
148            fn user_message(&self) -> String {
149                match self {
150                    $(
151                        Self::$variant(source) => {
152                            if let Some(forge_err) = (source as &dyn std::any::Any)
153                                .downcast_ref::<&(dyn $crate::error::ForgeError)>() 
154                            {
155                                return forge_err.user_message();
156                            }
157                            source.to_string()
158                        },
159                    )*
160                    $(
161                        Self::$variant_extra $({$($field),*})? => self.to_string(),
162                    )*
163                }
164            }
165            
166            fn caption(&self) -> &'static str {
167                match self {
168                    $(
169                        Self::$variant(source) => {
170                            if let Some(forge_err) = (source as &dyn std::any::Any)
171                                .downcast_ref::<&(dyn $crate::error::ForgeError)>() 
172                            {
173                                return forge_err.caption();
174                            }
175                            concat!(stringify!($variant), ": Error")
176                        },
177                    )*
178                    $(
179                        Self::$variant_extra $({$($field),*})? => {
180                            concat!(stringify!($variant_extra), ": Error")
181                        },
182                    )*
183                }
184            }
185            
186            fn is_retryable(&self) -> bool {
187                match self {
188                    $(
189                        Self::$variant(source) => {
190                            if let Some(forge_err) = (source as &dyn std::any::Any)
191                                .downcast_ref::<&(dyn $crate::error::ForgeError)>() 
192                            {
193                                return forge_err.is_retryable();
194                            }
195                            false
196                        },
197                    )*
198                    _ => false,
199                }
200            }
201            
202            fn status_code(&self) -> u16 {
203                match self {
204                    $(
205                        Self::$variant(source) => {
206                            if let Some(forge_err) = (source as &dyn std::any::Any)
207                                .downcast_ref::<&(dyn $crate::error::ForgeError)>() 
208                            {
209                                return forge_err.status_code();
210                            }
211                            500
212                        },
213                    )*
214                    _ => 500,
215                }
216            }
217            
218            fn exit_code(&self) -> i32 {
219                match self {
220                    $(
221                        Self::$variant(source) => {
222                            if let Some(forge_err) = (source as &dyn std::any::Any)
223                                .downcast_ref::<&(dyn $crate::error::ForgeError)>() 
224                            {
225                                return forge_err.exit_code();
226                            }
227                            1
228                        },
229                    )*
230                    _ => 1,
231                }
232            }
233        }
234    };
235}