Skip to main content

ap_error_macro/
lib.rs

1//! Proc macro for generating error types with FlatError trait implementation.
2//!
3//! This is a simplified version of bitwarden-error-macro that only supports
4//! the `flat` error type for CLI use.
5
6use darling::{FromMeta, ast::NestedMeta};
7use quote::quote;
8use syn::Data;
9
10/// Arguments for the ap_error macro
11#[derive(FromMeta)]
12struct ApErrorArgs {
13    #[darling(flatten)]
14    #[allow(dead_code)]
15    error_type: ApErrorType,
16}
17
18#[derive(FromMeta)]
19#[darling(rename_all = "snake_case")]
20enum ApErrorType {
21    /// The error is converted into a flat error using the `FlatError` trait
22    Flat,
23}
24
25/// A procedural macro for generating error types with FlatError trait implementation.
26///
27/// # Example
28///
29/// ```rust,ignore
30/// use ap_error::ap_error;
31/// use thiserror::Error;
32///
33/// #[derive(Debug, Error)]
34/// #[ap_error(flat)]
35/// enum MyError {
36///     #[error("Not found")]
37///     NotFound,
38///     #[error("Permission denied")]
39///     PermissionDenied,
40/// }
41/// ```
42#[proc_macro_attribute]
43pub fn ap_error(
44    args: proc_macro::TokenStream,
45    item: proc_macro::TokenStream,
46) -> proc_macro::TokenStream {
47    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
48        Ok(v) => v,
49        Err(e) => {
50            return proc_macro::TokenStream::from(darling::Error::from(e).write_errors());
51        }
52    };
53
54    let _args = match ApErrorArgs::from_list(&attr_args) {
55        Ok(params) => params,
56        Err(error) => {
57            return proc_macro::TokenStream::from(error.write_errors());
58        }
59    };
60
61    let input = syn::parse_macro_input!(item as syn::DeriveInput);
62    let type_identifier = &input.ident;
63
64    // Only flat mode is supported
65    ap_error_flat(&input, type_identifier)
66}
67
68fn ap_error_flat(
69    input: &syn::DeriveInput,
70    type_identifier: &proc_macro2::Ident,
71) -> proc_macro::TokenStream {
72    match &input.data {
73        Data::Enum(data) => {
74            let match_arms = data.variants.iter().map(|variant| {
75                let variant_ident = &variant.ident;
76                let variant_str = variant_ident.to_string();
77
78                match variant.fields {
79                    syn::Fields::Unit => {
80                        quote! {
81                            #type_identifier::#variant_ident => #variant_str
82                        }
83                    }
84                    syn::Fields::Named(_) => {
85                        quote! {
86                            #type_identifier::#variant_ident { .. } => #variant_str
87                        }
88                    }
89                    syn::Fields::Unnamed(_) => {
90                        quote! {
91                            #type_identifier::#variant_ident(..) => #variant_str
92                        }
93                    }
94                }
95            });
96
97            quote! {
98                #input
99
100                #[automatically_derived]
101                impl ::ap_error::flat_error::FlatError for #type_identifier {
102                    fn error_variant(&self) -> &'static str {
103                        match &self {
104                            #(#match_arms), *
105                        }
106                    }
107                }
108            }
109            .into()
110        }
111        _ => syn::Error::new_spanned(input, "ap_error can only be used with enums")
112            .to_compile_error()
113            .into(),
114    }
115}