macron_impl_error/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3//! See the documentation here [macron documentation](https://docs.rs/macron)
4
5use proc_macro::TokenStream;
6use quote::quote;
7
8/// The implementation of trait [Error](std::error::Error)
9#[proc_macro_derive(Error, attributes(source))]
10pub fn impl_error(input: TokenStream) -> TokenStream {
11    let syn::DeriveInput { ident, data, .. } = syn::parse_macro_input!(input as syn::DeriveInput);
12    
13    match data {
14        // impl Struct:
15        syn::Data::Struct(st) => {
16            let source = match st.fields {
17                syn::Fields::Named(fields) => {
18                    let src_field = fields.named
19                        .iter()
20                        .find(|f| f.ident
21                            .as_ref()
22                            .map(|i| i == "source")
23                            .unwrap_or(false)
24                        );
25            
26                    match src_field {
27                        Some(field) if is_option_type(&field.ty) => quote! { self.source.as_deref() },
28
29                        Some(_) => quote! { Some(self.source) },
30
31                        None => quote! { None }
32                    }
33                },
34
35                _ => quote! { None }
36            };
37            
38            quote! {
39                impl ::std::error::Error for #ident {
40                    fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
41                        #source
42                    }
43                }
44            }.into()
45        },
46
47        // impl Enum:
48        syn::Data::Enum(en) => {
49            let vars = en.variants
50                .into_iter()
51                .map(|syn::Variant { ident: var_ident, fields, .. }| {
52                    match fields {
53                        // Fields::Named { ..: .. }
54                        syn::Fields::Named(fields) => {
55                            let src_field = fields.named
56                                .iter()
57                                .find(|f| f.ident
58                                    .as_ref()
59                                    .map(|i| i == "source")
60                                    .unwrap_or(false)
61                                );
62                    
63                            match src_field {
64                                Some(field) if is_option_type(&field.ty) => quote! { Self::#var_ident { source, .. } => source.as_deref() },
65
66                                Some(_) => quote! { Self::#var_ident { source, .. } => Some(source) },
67
68                                None => quote! { Self::#var_ident(_) => None }
69                            }
70                        },
71
72                        // Fields::Unnamed(.., ..)
73                        syn::Fields::Unnamed(fields) => {
74                            let src_field = fields.unnamed
75                                .iter()
76                                .enumerate()
77                                .find(|(_, field)| field.attrs
78                                    .iter()
79                                    .any(|attr| attr.path().is_ident("source"))
80                                );
81
82                            let src_idx = src_field.map(|(idx, _)| idx).unwrap_or(0);
83                            let stubs = (0..src_idx).into_iter().map(|_| quote! { _ });
84
85                            match src_field {
86                                Some((_, field)) if is_option_type(&field.ty) => quote! { Self::#var_ident(#(#stubs,)* source) => source.as_deref() },
87
88                                Some(_) => quote! { Self::#var_ident(#(#stubs,)* source, ..) => Some(source) },
89
90                                None => quote! { Self::#var_ident(_) => None }
91                            }
92                        },
93
94                        // Self::Unit
95                        syn::Fields::Unit => quote! { Self::#var_ident => None }
96                    }
97                });
98            
99            quote! {
100                impl ::std::error::Error for #ident {
101                    fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
102                        match &self {
103                            #(
104                                #vars,
105                            )*
106                        }
107                    }
108                }
109            }.into()
110        },
111
112        _ => panic!("the expected a 'struct' or 'enum'")
113    }
114}
115
116// Do check if a type is "Option"
117fn is_option_type(ty: &syn::Type) -> bool {
118    if let syn::Type::Path(type_path) = ty {
119        type_path.path.segments.last().map_or(false, |seg| seg.ident == "Option")
120    } else {
121        false
122    }
123}