infinite_errors_macros/
lib.rs

1extern crate proc_macro;
2
3#[cfg(test)]
4mod test;
5
6use proc_macro::TokenStream;
7use quote::{quote, ToTokens};
8use syn::{parse_quote, ItemFn, TypePath};
9
10/// Add a context when this function returns an error.
11///
12/// This macro takes a single argument: the error kind which serves as
13/// context. This macro is compatible with both sync and async functions. The
14/// function must return a `Result<T, Error>` for some `T`, where `Error` is
15/// an error type generated by the `declare_error_type` macro.
16///
17/// ```ignore
18/// #[err_context(ErrorKind::Parse)]
19/// fn parse(s: &str) -> Result<SomeStruct, Error> {
20///     // ...
21/// }
22/// ```
23#[proc_macro_attribute]
24pub fn err_context(attributes: TokenStream, item: TokenStream) -> TokenStream {
25    err_context_impl(attributes.into(), item.into()).into()
26}
27
28fn err_context_impl(
29    attributes: proc_macro2::TokenStream,
30    item: proc_macro2::TokenStream,
31) -> proc_macro2::TokenStream {
32    let context_error_kind = unwrap_parse("attributes", syn::parse2::<TypePath>(attributes));
33    let mut item_fn = unwrap_parse("item", syn::parse2::<ItemFn>(item));
34    let (left, right) = if item_fn.sig.asyncness.is_some() {
35        (quote! {async move}, quote! {.await})
36    } else {
37        (quote! {move | |}, quote! {()})
38    };
39    let block = item_fn.block;
40    let body = parse_quote! {{
41        ::infinite_errors::ErrorContext::err_context(
42            (#left
43                #block
44            )#right,
45            #context_error_kind
46        )
47    }};
48    item_fn.block = Box::new(body);
49
50    item_fn.into_token_stream()
51}
52
53fn unwrap_parse<T>(name: &str, res: syn::parse::Result<T>) -> T {
54    match res {
55        Ok(x) => x,
56        Err(err) => panic!("failed to parse {name}: {err}"),
57    }
58}