derive_enum_error/
lib.rs

1//! # `derive_enum_error`
2//!
3//! ## Deriving error sources
4//!
5//! Add an `#[error(source)]` attribute to the field:
6//!
7//! ```
8//! use derive_enum_error::Error;
9//!
10//! use std::io;
11//!
12//! /// `MyError::source` will return a reference to the `io_error` field
13//! #[derive(Debug, Error)]
14//! #[error(display = "An error occurred.")]
15//! struct MyError {
16//!     #[error(source)]
17//!     io_error: io::Error,
18//! }
19//! #
20//! # fn main() {}
21//! ```
22//!
23//! ## Formatting fields
24//!
25//! ```rust
26//! use derive_enum_error::Error;
27//!
28//! use std::path::PathBuf;
29//!
30//! #[derive(Debug, Error)]
31//! pub enum FormatError {
32//!     #[error(display = "invalid header (expected: {:?}, got: {:?})", expected, found)]
33//!     InvalidHeader {
34//!         expected: String,
35//!         found: String,
36//!     },
37//!     // Note that tuple fields need to be prefixed with `_`
38//!     #[error(display = "missing attribute: {:?}", _0)]
39//!     MissingAttribute(String),
40//!
41//! }
42//!
43//! #[derive(Debug, Error)]
44//! pub enum LoadingError {
45//!     #[error(display = "could not decode file")]
46//!     FormatError(#[error(source)] FormatError),
47//!     #[error(display = "could not find file: {:?}", path)]
48//!     NotFound { path: PathBuf },
49//! }
50//! #
51//! # fn main() {}
52//! ```
53//!
54//! ## Printing the error
55//!
56//! ```
57//! use std::error::Error;
58//!
59//! fn print_error(e: &dyn Error) {
60//!     eprintln!("error: {}", e);
61//!     let mut source = e.source();
62//!     while let Some(e) = source {
63//!         eprintln!("sourced by: {}", e);
64//!         source = e.source();
65//!     }
66//! }
67//! ```
68//!
69#![recursion_limit = "192"]
70
71use proc_macro2::TokenStream;
72use quote::quote_spanned;
73use synstructure::decl_derive;
74
75macro_rules! quote {
76    ($($t:tt)*) => (quote_spanned!(proc_macro2::Span::call_site() => $($t)*))
77}
78
79decl_derive!([Error, attributes(error, source)] => error);
80
81fn error(s: synstructure::Structure) -> TokenStream {
82    let source_body = s.each_variant(|v| {
83        if let Some(source) = v.bindings().iter().find(is_source) {
84            quote!(return Some(#source as & ::std::error::Error))
85        } else {
86            quote!(return None)
87        }
88    });
89
90    let source_method = quote! {
91        #[allow(unreachable_code)]
92        fn source(&self) -> ::std::option::Option<&(::std::error::Error + 'static)> {
93            match *self { #source_body }
94            None
95        }
96    };
97
98    let error = s.unbound_impl(
99        quote!(::std::error::Error),
100        quote! {
101            fn description(&self) -> &str {
102                "description() is deprecated; use Display"
103            }
104
105            #source_method
106        },
107    );
108
109    let display = display_body(&s).map(|display_body| {
110        s.unbound_impl(
111            quote!(::std::fmt::Display),
112            quote! {
113                #[allow(unreachable_code)]
114                fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
115                    match *self { #display_body }
116                    write!(f, "An error has occurred.")
117                }
118            },
119        )
120    });
121
122    (quote! {
123        #error
124        #display
125    })
126    .into()
127}
128
129fn display_body(s: &synstructure::Structure) -> Option<quote::__rt::TokenStream> {
130    let mut msgs = s.variants().iter().map(|v| find_error_msg(&v.ast().attrs));
131    if msgs.all(|msg| msg.is_none()) {
132        return None;
133    }
134
135    Some(s.each_variant(|v| {
136        let msg =
137            find_error_msg(&v.ast().attrs).expect("All variants must have display attribute.");
138        if msg.nested.is_empty() {
139            panic!("Expected at least one argument to error attribute");
140        }
141
142        let format_string = match msg.nested[0] {
143            syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv)) if nv.ident == "display" => {
144                nv.lit.clone()
145            }
146            _ => panic!(
147                "Error attribute must begin `display = \"\"` to control the Display message."
148            ),
149        };
150        let args = msg.nested.iter().skip(1).map(|arg| match *arg {
151            syn::NestedMeta::Literal(syn::Lit::Int(ref i)) => {
152                let bi = &v.bindings()[i.value() as usize];
153                quote!(#bi)
154            }
155            syn::NestedMeta::Meta(syn::Meta::Word(ref id)) => {
156                let id_s = id.to_string();
157                if id_s.starts_with("_") {
158                    if let Ok(idx) = id_s[1..].parse::<usize>() {
159                        let bi = match v.bindings().get(idx) {
160                            Some(bi) => bi,
161                            None => {
162                                panic!(
163                                    "display attempted to access field `{}` in `{}::{}` which \
164                                     does not exist (there are {} field{})",
165                                    idx,
166                                    s.ast().ident,
167                                    v.ast().ident,
168                                    v.bindings().len(),
169                                    if v.bindings().len() != 1 { "s" } else { "" }
170                                );
171                            }
172                        };
173                        return quote!(#bi);
174                    }
175                }
176                for bi in v.bindings() {
177                    if bi.ast().ident.as_ref() == Some(id) {
178                        return quote!(#bi);
179                    }
180                }
181                panic!(
182                    "Couldn't find field `{}` in `{}::{}`",
183                    id,
184                    s.ast().ident,
185                    v.ast().ident
186                );
187            }
188            _ => panic!("Invalid argument to error attribute!"),
189        });
190
191        quote! {
192            return write!(f, #format_string #(, #args)*)
193        }
194    }))
195}
196
197fn find_error_msg(attrs: &[syn::Attribute]) -> Option<syn::MetaList> {
198    let mut error_msg = None;
199    for attr in attrs {
200        if let Some(meta) = attr.interpret_meta() {
201            if meta.name() == "error" {
202                if error_msg.is_some() {
203                    panic!("Cannot have two display attributes")
204                } else {
205                    if let syn::Meta::List(list) = meta {
206                        error_msg = Some(list);
207                    } else {
208                        panic!("error attribute must take a list in parentheses")
209                    }
210                }
211            }
212        }
213    }
214    error_msg
215}
216
217fn is_source(bi: &&synstructure::BindingInfo) -> bool {
218    let mut found_source = false;
219    for attr in &bi.ast().attrs {
220        if let Some(meta) = attr.interpret_meta() {
221            if meta.name() == "source" {
222                if found_source {
223                    panic!("Cannot have two `source` attributes");
224                }
225                found_source = true;
226            }
227            if meta.name() == "error" {
228                if let syn::Meta::List(ref list) = meta {
229                    if let Some(ref pair) = list.nested.first() {
230                        if let &&syn::NestedMeta::Meta(syn::Meta::Word(ref word)) = pair.value() {
231                            if word == "source" {
232                                if found_source {
233                                    panic!("Cannot have two `source` attributes");
234                                }
235                                found_source = true;
236                            }
237                        }
238                    }
239                }
240            }
241        }
242    }
243    found_source
244}