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