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}