1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
6use quote::quote;
7
8#[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 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 syn::Data::Enum(en) => {
49 let vars = en.variants
50 .into_iter()
51 .map(|syn::Variant { ident: var_ident, fields, .. }| {
52 match fields {
53 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 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 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
116fn 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}