error_support_macros/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{parse_quote, spanned::Spanned};
7
8const ERR_MSG: &str = "Expected #[handle_error(path::to::Error)]";
9
10/// A procedural macro that exposes internal errors to external errors the
11/// consuming applications should handle. It requires that the internal error
12/// implements [`error_support::ErrorHandling`].
13///
14/// Additionally, this procedural macro has side effects, including:
15/// * It would log the error based on a pre-defined log level. The log level is defined
16///   in the [`error_support::ErrorHandling`] implementation.
17/// * It would report some errors using an external error reporter, in practice, this
18///   is implemented using Sentry in the app.
19///
20/// # Example
21/// ```ignore
22/// use error_support::{handle_error, GetErrorHandling, ErrorHandling};
23/// use std::fmt::Display
24///#[derive(Debug, thiserror::Error)]
25/// struct Error {}
26/// type Result<T, E = Error> = std::result::Result<T, E>;
27///
28/// impl Display for Error {
29///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30///         write!(f, "Internal Error!")
31///     }
32/// }
33///
34/// #[derive(Debug, thiserror::Error)]
35/// struct ExternalError {}
36///
37/// impl Display for ExternalError {
38///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39///         write!(f, "External Error!")
40///     }
41/// }
42///
43/// impl GetErrorHandling for Error {
44///    type ExternalError = ExternalError;
45///
46///    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
47///        ErrorHandling::convert(ExternalError {})
48///    }
49/// }
50///
51/// // The `handle_error` macro maps from the error supplied in the mandatory argument
52/// // (ie, `Error` in this example) to the error returned by the function (`ExternalError`
53/// // in this example)
54/// #[handle_error(Error)]
55/// fn do_something() -> std::result::Result<String, ExternalError> {
56///    Err(Error{})
57/// }
58///
59/// // The error here is an `ExternalError`
60/// let _: ExternalError = do_something().unwrap_err();
61/// ```
62#[proc_macro_attribute]
63pub fn handle_error(args: TokenStream, input: TokenStream) -> TokenStream {
64    let mut err_path = None;
65    let parser = syn::meta::parser(|meta| {
66        if meta.input.is_empty() && err_path.replace(meta.path).is_none() {
67            Ok(())
68        } else {
69            Err(syn::Error::new(meta.input.span(), ERR_MSG))
70        }
71    });
72    TokenStream::from(
73        match syn::parse::Parser::parse(parser, args)
74            .map_err(|e| syn::Error::new(e.span(), ERR_MSG))
75            .and_then(|()| syn::parse::<syn::Item>(input))
76            .and_then(|parsed| impl_handle_error(&parsed, err_path.unwrap()))
77        {
78            Ok(res) => res,
79            Err(e) => e.to_compile_error(),
80        },
81    )
82}
83
84fn impl_handle_error(
85    input: &syn::Item,
86    err_path: syn::Path,
87) -> syn::Result<proc_macro2::TokenStream> {
88    if let syn::Item::Fn(item_fn) = input {
89        let original_body = &item_fn.block;
90
91        let mut new_fn = item_fn.clone();
92        new_fn.block = parse_quote! {
93            {
94                (|| -> ::std::result::Result<_, #err_path> {
95                    #original_body
96                })().map_err(::error_support::convert_log_report_error)
97            }
98        };
99
100        Ok(quote! {
101            #new_fn
102        })
103    } else {
104        Err(syn::Error::new(
105            input.span(),
106            "#[handle_error(..)] can only be used on functions",
107        ))
108    }
109}