error_forge_derive/
lib.rs1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::{quote, format_ident};
4use syn::{parse_macro_input, DeriveInput, Data, Fields};
5
6#[proc_macro_derive(ModError, attributes(error_prefix, error_display, error_kind,
37 error_caption, error_retryable, error_http_status,
38 error_exit_code))]
39pub fn derive_mod_error(input: TokenStream) -> TokenStream {
40 let input = parse_macro_input!(input as DeriveInput);
42
43 let is_enum = match &input.data {
45 Data::Enum(_) => true,
46 Data::Struct(_) => false,
47 Data::Union(_) => panic!("ModError cannot be derived for unions"),
48 };
49
50 let error_prefix = get_error_prefix(&input.attrs);
52
53 let implementation = if is_enum {
55 implement_for_enum(&input, &error_prefix)
56 } else {
57 implement_for_struct(&input, &error_prefix)
58 };
59
60 TokenStream::from(implementation)
62}
63
64fn get_error_prefix(attrs: &[syn::Attribute]) -> String {
66 for attr in attrs {
67 if attr.path.is_ident("error_prefix") {
68 if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() {
71 if let syn::Lit::Str(lit) = meta.lit {
72 return lit.value();
73 }
74 }
75 else if let Ok(syn::Meta::List(meta)) = attr.parse_meta() {
77 if let Some(nested) = meta.nested.iter().next() {
78 if let syn::NestedMeta::Lit(syn::Lit::Str(lit)) = nested {
79 return lit.value();
80 }
81 }
82 }
83 }
84 }
85 String::new()
86}
87
88fn implement_for_enum(input: &DeriveInput, error_prefix: &str) -> proc_macro2::TokenStream {
90 let name = &input.ident;
91 let data_enum = match &input.data {
92 Data::Enum(data) => data,
93 _ => panic!("Expected enum"),
94 };
95
96 let mut kind_match_arms = Vec::new();
98 let mut caption_match_arms = Vec::new();
99 let mut display_match_arms = Vec::new();
100 let mut retryable_match_arms = Vec::new();
101 let mut status_code_match_arms = Vec::new();
102 let mut exit_code_match_arms = Vec::new();
103
104 for variant in &data_enum.variants {
106 let variant_name = &variant.ident;
107 let variant_name_str = variant_name.to_string();
108
109 let mut display_format = variant_name_str.clone();
111 let mut retryable = false;
112 let mut status_code: u16 = 500;
113 let mut exit_code: i32 = 1;
114
115 for attr in &variant.attrs {
117 if attr.path.is_ident("error_display") {
118 if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() {
119 if let syn::Lit::Str(lit) = meta.lit {
120 display_format = lit.value();
121 }
122 }
123 } else if attr.path.is_ident("error_retryable") {
124 retryable = true;
125 } else if attr.path.is_ident("error_http_status") {
126 if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() {
127 if let syn::Lit::Int(lit) = meta.lit {
128 status_code = lit.base10_parse().unwrap_or(500);
129 }
130 }
131 } else if attr.path.is_ident("error_exit_code") {
132 if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() {
133 if let syn::Lit::Int(lit) = meta.lit {
134 exit_code = lit.base10_parse().unwrap_or(1);
135 }
136 }
137 }
138 }
139
140 match &variant.fields {
142 Fields::Named(fields) => {
143 let field_names: Vec<_> = fields.named.iter()
144 .map(|f| f.ident.as_ref().unwrap())
145 .collect();
146
147 kind_match_arms.push(quote! {
150 Self::#variant_name { .. } => #variant_name_str
151 });
152
153 caption_match_arms.push(quote! {
154 Self::#variant_name { .. } => concat!(#error_prefix, ": Error")
155 });
156
157 let _field_patterns = field_names.iter().map(|name| {
158 let _name_str = name.to_string();
159 quote! { #name, }
160 });
161
162 display_match_arms.push(quote! {
164 Self::#variant_name { .. } => format!("{}: {}", #error_prefix, #display_format)
165 });
166
167 retryable_match_arms.push(quote! {
168 Self::#variant_name { .. } => #retryable
169 });
170
171 status_code_match_arms.push(quote! {
172 Self::#variant_name { .. } => #status_code
173 });
174
175 exit_code_match_arms.push(quote! {
176 Self::#variant_name { .. } => #exit_code
177 });
178 },
179 Fields::Unnamed(fields) => {
180 let field_count = fields.unnamed.len();
181 let field_names: Vec<_> = (0..field_count)
182 .map(|i| format_ident!("_{}", i))
183 .collect();
184
185 kind_match_arms.push(quote! {
190 Self::#variant_name(..) => #variant_name_str
191 });
192
193 caption_match_arms.push(quote! {
194 Self::#variant_name(..) => concat!(#error_prefix, ": Error")
195 });
196
197 let _field_patterns = field_names.iter().map(|name| {
198 quote! { #name, }
199 });
200
201 if display_format.contains("{0}") || display_format.contains("{}") {
203 let field_pattern_list = field_names.iter().map(|name| quote! { #name, });
205 display_match_arms.push(quote! {
206 Self::#variant_name(#(#field_pattern_list)*) => format!("{}: {}", #error_prefix, format!(#display_format #(, #field_names)*))
207 });
208 } else {
209 display_match_arms.push(quote! {
211 Self::#variant_name(..) => format!("{}: {}", #error_prefix, #display_format)
212 });
213 }
214
215 retryable_match_arms.push(quote! {
216 Self::#variant_name(..) => #retryable
217 });
218
219 status_code_match_arms.push(quote! {
220 Self::#variant_name(..) => #status_code
221 });
222
223 exit_code_match_arms.push(quote! {
224 Self::#variant_name(..) => #exit_code
225 });
226 },
227 Fields::Unit => {
228 kind_match_arms.push(quote! {
230 Self::#variant_name => #variant_name_str
231 });
232
233 caption_match_arms.push(quote! {
234 Self::#variant_name => concat!(#error_prefix, ": Error")
235 });
236
237 display_match_arms.push(quote! {
238 Self::#variant_name => format!("{}: {}", #error_prefix, #display_format)
239 });
240
241 retryable_match_arms.push(quote! {
242 Self::#variant_name => #retryable
243 });
244
245 status_code_match_arms.push(quote! {
246 Self::#variant_name => #status_code
247 });
248
249 exit_code_match_arms.push(quote! {
250 Self::#variant_name => #exit_code
251 });
252 },
253 }
254 }
255
256 quote! {
258 impl ::std::fmt::Display for #name {
259 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
260 let msg = match self {
261 #(#display_match_arms,)*
262 };
263 write!(f, "{}", msg)
264 }
265 }
266
267 impl ::error_forge::error::ForgeError for #name {
268 fn kind(&self) -> &'static str {
269 match self {
270 #(#kind_match_arms,)*
271 }
272 }
273
274 fn caption(&self) -> &'static str {
275 match self {
276 #(#caption_match_arms,)*
277 }
278 }
279
280 fn is_retryable(&self) -> bool {
281 match self {
282 #(#retryable_match_arms,)*
283 }
284 }
285
286 fn status_code(&self) -> u16 {
287 match self {
288 #(#status_code_match_arms,)*
289 }
290 }
291
292 fn exit_code(&self) -> i32 {
293 match self {
294 #(#exit_code_match_arms,)*
295 }
296 }
297 }
298
299 impl ::std::error::Error for #name {
300 fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
301 None
302 }
303 }
304 }
305}
306
307fn implement_for_struct(input: &DeriveInput, error_prefix: &str) -> proc_macro2::TokenStream {
309 let name = &input.ident;
310 let name_str = name.to_string();
311
312 quote! {
313 impl ::std::fmt::Display for #name {
314 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
315 write!(f, "{}: Error", #error_prefix)
316 }
317 }
318
319 impl ::error_forge::error::ForgeError for #name {
320 fn kind(&self) -> &'static str {
321 #name_str
322 }
323
324 fn caption(&self) -> &'static str {
325 concat!(#error_prefix, ": Error")
326 }
327 }
328
329 impl ::std::error::Error for #name {
330 fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
331 None
332 }
333 }
334 }
335}
336
337