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}