1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_quote, spanned::Spanned};

const ERR_MSG: &str = "Expected #[handle_error(path::to::Error)]";

/// A procedural macro that exposes internal errors to external errors the
/// consuming applications should handle. It requires that the internal error
/// implements [`error_support::ErrorHandling`].
///
/// Additionally, this procedural macro has side effects, including:
/// * It would log the error based on a pre-defined log level. The log level is defined
///  in the [`error_support::ErrorHandling`] implementation.
/// * It would report some errors using an external error reporter, in practice, this
///   is implemented using Sentry in the app.
///
/// # Example
/// ```ignore
/// use error_support::{handle_error, GetErrorHandling, ErrorHandling};
/// use std::fmt::Display
///#[derive(Debug, thiserror::Error)]
/// struct Error {}
/// type Result<T, E = Error> = std::result::Result<T, E>;

/// impl Display for Error {
///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
///         write!(f, "Internal Error!")
///     }
/// }
///
/// #[derive(Debug, thiserror::Error)]
/// struct ExternalError {}
///
/// impl Display for ExternalError {
///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
///         write!(f, "External Error!")
///     }
/// }
///
/// impl GetErrorHandling for Error {
///    type ExternalError = ExternalError;
///
///    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
///        ErrorHandling::convert(ExternalError {})
///    }
/// }
///
/// // The `handle_error` macro maps from the error supplied in the mandatory argument
/// // (ie, `Error` in this example) to the error returned by the function (`ExternalError`
/// // in this example)
/// #[handle_error(Error)]
/// fn do_something() -> std::result::Result<String, ExternalError> {
///    Err(Error{})
/// }
///
/// // The error here is an `ExternalError`
/// let _: ExternalError = do_something().unwrap_err();
/// ```
#[proc_macro_attribute]
pub fn handle_error(args: TokenStream, input: TokenStream) -> TokenStream {
    let mut err_path = None;
    let parser = syn::meta::parser(|meta| {
        if meta.input.is_empty() && err_path.replace(meta.path).is_none() {
            Ok(())
        } else {
            Err(syn::Error::new(meta.input.span(), ERR_MSG))
        }
    });
    TokenStream::from(
        match syn::parse::Parser::parse(parser, args)
            .map_err(|e| syn::Error::new(e.span(), ERR_MSG))
            .and_then(|()| syn::parse::<syn::Item>(input))
            .and_then(|parsed| impl_handle_error(&parsed, err_path.unwrap()))
        {
            Ok(res) => res,
            Err(e) => e.to_compile_error(),
        },
    )
}

fn impl_handle_error(
    input: &syn::Item,
    err_path: syn::Path,
) -> syn::Result<proc_macro2::TokenStream> {
    if let syn::Item::Fn(item_fn) = input {
        let original_body = &item_fn.block;

        let mut new_fn = item_fn.clone();
        new_fn.block = parse_quote! {
            {
                (|| -> ::std::result::Result<_, #err_path> {
                    #original_body
                })().map_err(::error_support::convert_log_report_error)
            }
        };

        Ok(quote! {
            #new_fn
        })
    } else {
        Err(syn::Error::new(
            input.span(),
            "#[handle_error(..)] can only be used on functions",
        ))
    }
}