error_rules/
lib.rs

1//! # error-rules
2//!
3//! [![Latest Version](https://img.shields.io/crates/v/error-rules.svg)](https://crates.io/crates/error-rules)
4//! [![docs](https://docs.rs/error-rules/badge.svg)](https://docs.rs/error-rules)
5//!
6//! ## Intro
7//!
8//! error-rules is a derive macro to implement error handler.
9//! Error handler based on the enum.
10//! Macro automatically implements conversion of any error type into the inner enum field.
11//!
12//! ## Error conversion
13//!
14//! `#[error_from]` attribute implements an automatically conversion from any error type.
15//! Converted type should implements `std::error::Error` interface.
16//!
17//! ```rust
18//! use error_rules::*;
19//!
20//! #[derive(Debug, Error)]
21//! enum AppError {
22//!     #[error_from("App IO: {}", 0)]
23//!     Io(std::io::Error),
24//! }
25//!
26//! type Result<T> = std::result::Result<T, AppError>;
27//!
28//! fn example() -> Result<()> {
29//!     let _file = std::fs::File::open("not-found.txt")?;
30//!     unreachable!()
31//! }
32//!
33//! let error = example().unwrap_err();
34//! assert_eq!(error.to_string().as_str(),
35//!     "App IO: No such file or directory (os error 2)");
36//! ```
37//!
38//! ## Custom error kind
39//!
40//! `#[error_kind]` attribute describes custom error kind.
41//! Could be defined without fields or with fields tuple.
42//!
43//! ```rust
44//! use error_rules::*;
45//!
46//! #[derive(Debug, Error)]
47//! enum AppError {
48//!     #[error_kind("App: error without arguments")]
49//!     E1,
50//!     #[error_kind("App: code:{} message:{}", 0, 1)]
51//!     E2(usize, String),
52//! }
53//!
54//! type Result<T> = std::result::Result<T, AppError>;
55//!
56//! fn example_1() -> Result<()> {
57//!     Err(AppError::E1)
58//! }
59//!
60//! fn example_2() -> Result<()> {
61//!     Err(AppError::E2(404, "Not Found".to_owned()))
62//! }
63//!
64//! let error = example_1().unwrap_err();
65//! assert_eq!(error.to_string().as_str(),
66//!     "App: error without arguments");
67//!
68//! let error = example_2().unwrap_err();
69//! assert_eq!(error.to_string().as_str(),
70//!     "App: code:404 message:Not Found");
71//! ```
72//!
73//! ## Display attributes
74//!
75//! `#[error_from]` and `#[error_kind]` contain list of attributes to display error.
76//! First attribute should be literal string. Other attributes is a number of the
77//! unnamed field in the tuple. Started from 0.
78//!
79//! `#[error_from]` could defined without attributes it's equal to `#[error_from("{}", 0)]`
80//!
81//! ## Error prefix
82//!
83//! `#[error_prefix]` attribute should be defined before enum declaration and
84//! appends prefix into error text.
85//!
86//! ```rust
87//! use error_rules::*;
88//!
89//! #[derive(Debug, Error)]
90//! #[error_prefix = "App"]
91//! enum AppError {
92//!     #[error_from]
93//!     Io(std::io::Error),
94//! }
95//!
96//! type Result<T> = std::result::Result<T, AppError>;
97//!
98//! fn example() -> Result<()> {
99//!     let _file = std::fs::File::open("not-found.txt")?;
100//!     unreachable!()
101//! }
102//!
103//! let error = example().unwrap_err();
104//! assert_eq!(error.to_string().as_str(),
105//!     "App: No such file or directory (os error 2)");
106//! ```
107//!
108//! ## Error chain
109//!
110//! By implementing error for nested modules the primary error handler returns full chain of the error.
111//!
112//! ```rust
113//! use error_rules::*;
114//!
115//! #[derive(Debug, Error)]
116//! #[error_prefix = "Mod"]
117//! enum ModError {
118//!     #[error_from]
119//!     Io(std::io::Error),
120//! }
121//!
122//! fn mod_example() -> Result<(), ModError> {
123//!     let _file = std::fs::File::open("not-found.txt")?;
124//!     unreachable!()
125//! }
126//!
127//! #[derive(Debug, Error)]
128//! #[error_prefix = "App"]
129//! enum AppError {
130//!     #[error_from]
131//!     Mod(ModError),
132//! }
133//!
134//! fn app_example() -> Result<(), AppError> {
135//!     mod_example()?;
136//!     unreachable!()
137//! }
138//!
139//! let error = app_example().unwrap_err();
140//! assert_eq!(error.to_string().as_str(),
141//!     "App: Mod: No such file or directory (os error 2)");
142//! ```
143
144extern crate proc_macro;
145
146use proc_macro2::{TokenStream, Span, Ident};
147use quote::quote;
148use syn::{
149    self,
150    parse_macro_input,
151};
152
153
154fn impl_display_item(meta_list: &syn::MetaList) -> TokenStream {
155    let mut attr_list = TokenStream::new();
156
157    let fmt = match &meta_list.nested[0] {
158        syn::NestedMeta::Lit(syn::Lit::Str(v)) => v.value(),
159        _ => panic!("first attribute shoud be literal"),
160    };
161    attr_list.extend(quote! { #fmt });
162
163    for attr in meta_list.nested.iter().skip(1) {
164        let attr = match attr {
165            syn::NestedMeta::Lit(syn::Lit::Int(v)) => v.base10_parse::<u32>().unwrap(),
166            _ => panic!("attributes should be number"),
167        };
168
169        let attr_id = Ident::new(&format!("i{}", attr), Span::call_site());
170        attr_list.extend(quote! { , #attr_id });
171    }
172
173    attr_list
174}
175
176
177struct ErrorRules {
178    enum_id: Ident,
179    prefix: String,
180    from_list: TokenStream,
181    source_list: TokenStream,
182    display_list: TokenStream,
183}
184
185
186impl ErrorRules {
187    fn new(ident: &Ident) -> ErrorRules {
188        ErrorRules {
189            enum_id: ident.clone(),
190            prefix: String::default(),
191            from_list: TokenStream::default(),
192            source_list: TokenStream::default(),
193            display_list: TokenStream::default(),
194        }
195    }
196
197    fn impl_error_from_fields(&mut self,
198        item_id: &TokenStream,
199        variant: &syn::Variant)
200    {
201        let enum_id = &self.enum_id;
202
203        match &variant.fields {
204            syn::Fields::Unnamed(fields) => {
205                if fields.unnamed.len() != 1 {
206                    panic!("variant should contain one field")
207                }
208                let field = &fields.unnamed[0];
209                let ty = &field.ty;
210                self.from_list.extend(quote! {
211                    impl From<#ty> for #enum_id {
212                        #[inline]
213                        fn from(e: #ty) -> #enum_id { #item_id ( e ) }
214                    }
215                });
216                self.source_list.extend(quote! {
217                    #item_id (i0) => Some(i0),
218                });
219            }
220            _ => panic!("field format mismatch"),
221        };
222    }
223
224    fn impl_error_from_path(&mut self,
225        item_id: &TokenStream,
226        variant: &syn::Variant)
227    {
228        self.impl_error_from_fields(&item_id, variant);
229
230        self.display_list.extend(quote! {
231            #item_id ( i0 ) => write!(f, "{}", i0),
232        });
233    }
234
235    fn impl_error_from_list(&mut self,
236        item_id: &TokenStream,
237        variant: &syn::Variant,
238        meta_list: &syn::MetaList)
239    {
240        if meta_list.nested.is_empty() {
241            self.impl_error_from_path(item_id, variant);
242            return
243        }
244
245        self.impl_error_from_fields(item_id, variant);
246
247        let w = impl_display_item(meta_list);
248        self.display_list.extend(quote! {
249            #item_id ( i0 ) => write!(f, #w),
250        });
251    }
252
253    fn impl_error_from(&mut self,
254        item_id: &TokenStream,
255        variant: &syn::Variant,
256        meta: &syn::Meta)
257    {
258        match meta {
259            syn::Meta::Path(_) => self.impl_error_from_path(item_id, variant),
260            syn::Meta::List(v) => self.impl_error_from_list(item_id, variant, v),
261            _ => panic!("meta format mismatch"),
262        }
263    }
264
265    fn impl_error_kind_list(&mut self,
266        item_id: &TokenStream,
267        variant: &syn::Variant,
268        meta_list: &syn::MetaList)
269    {
270        if meta_list.nested.is_empty() {
271            panic!("meta format mismatch")
272        }
273
274        match &variant.fields {
275            syn::Fields::Unit => {
276                let w = impl_display_item(meta_list);
277                self.display_list.extend(quote! {
278                    #item_id => write!(f, #w),
279                });
280            }
281            syn::Fields::Unnamed(fields) => {
282                let mut ident_list = TokenStream::new();
283                for i in 0 .. fields.unnamed.len() {
284                    let field_id = Ident::new(&format!("i{}", i), Span::call_site());
285                    ident_list.extend(quote! { #field_id, });
286                }
287
288                let w = impl_display_item(meta_list);
289                self.display_list.extend(quote! {
290                    #item_id ( #ident_list ) => write!(f, #w),
291                });
292            }
293            _ => panic!("field format mismatch"),
294        };
295    }
296
297    fn impl_error_kind(&mut self,
298        item_id: &TokenStream,
299        variant: &syn::Variant,
300        meta: &syn::Meta)
301    {
302        match meta {
303            syn::Meta::List(v) => self.impl_error_kind_list(item_id, variant, v),
304            _ => panic!("meta format mismatch"),
305        }
306    }
307
308    fn impl_variant(&mut self, variant: &syn::Variant) {
309        let enum_id = &self.enum_id;
310        let item_id = &variant.ident;
311        let item_id = quote! { #enum_id::#item_id };
312
313        for attr in variant.attrs.iter().filter(|v| v.path.segments.len() == 1) {
314            match attr.path.segments[0].ident.to_string().as_str() {
315                "error_from" => {
316                    let meta = attr.parse_meta().unwrap();
317                    self.impl_error_from(&item_id, variant, &meta);
318                    break
319                }
320                "error_kind" => {
321                    let meta = attr.parse_meta().unwrap();
322                    self.impl_error_kind(&item_id, variant, &meta);
323                    break
324                }
325                _ => {},
326            }
327        }
328    }
329
330    fn build(&mut self, data: &syn::DataEnum) -> TokenStream {
331        for variant in &data.variants {
332            self.impl_variant(variant);
333        }
334
335        let enum_id = &self.enum_id;
336        let display_list = &self.display_list;
337        let source_list = &self.source_list;
338        let from_list = &self.from_list;
339
340        let mut display_prefix = TokenStream::new();
341        if ! self.prefix.is_empty() {
342            let prefix = &self.prefix;
343            display_prefix.extend(quote! {
344                write!(f, "{}: ", #prefix)?;
345            });
346        }
347
348        quote! {
349            impl std::fmt::Display for #enum_id {
350                fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
351                    #display_prefix
352                    match self {
353                        #display_list
354                    }
355                }
356            }
357
358            impl std::error::Error for #enum_id {
359                fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
360                    match self {
361                        #source_list
362                        _ => None,
363                    }
364                }
365            }
366
367            impl From<#enum_id> for std::io::Error {
368                fn from(error: #enum_id) -> Self {
369                    Self::new(std::io::ErrorKind::Other, error)
370                }
371            }
372
373            #from_list
374        }
375    }
376
377    fn set_attrs(&mut self, attrs: &Vec<syn::Attribute>) {
378        for attr in attrs.iter().filter(|v| v.path.segments.len() == 1) {
379            match attr.path.segments[0].ident.to_string().as_str() {
380                "error_prefix" => {
381                    if let syn::Meta::NameValue(v) = &attr.parse_meta().unwrap() {
382                        if let syn::Lit::Str(v) = &v.lit {
383                            self.prefix = v.value();
384                            break
385                        }
386                    }
387                    panic!("meta format mismatch")
388                }
389                _ => {},
390            }
391        }
392    }
393}
394
395
396#[proc_macro_derive(Error, attributes(error_from, error_kind, error_prefix))]
397pub fn error_rules_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
398    let input = parse_macro_input!(input as syn::DeriveInput);
399
400    if let syn::Data::Enum(ref s) = input.data {
401        let mut error_rules = ErrorRules::new(&input.ident);
402        error_rules.set_attrs(&input.attrs);
403        error_rules.build(s).into()
404    } else {
405        panic!("enum required")
406    }
407}