monetary_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use proc_macro_crate::{crate_name, FoundCrate};
6use quote::quote;
7use syn::{parse_macro_input, Ident, ItemStruct};
8
9#[proc_macro_attribute]
10/// This macro:
11/// - Ensures that the input item is a Zero-sized struct
12/// - Derives Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Default
13/// - Derives Serialize, Deserialize if the feature "serde" is enabled
14/// - Derives JsonSchema if the feature "schemars" is enabled
15/// - Adds the `unsafe` Denomination trait to the input item
16pub fn denom(_attr: TokenStream, item: TokenStream) -> TokenStream {
17    // Parse the input token stream into a syntax tree
18    let input = parse_macro_input!(item as ItemStruct);
19
20    // Ensure the struct is zero-sized
21    if !input.fields.is_empty() {
22        return syn::Error::new_spanned(input, "Struct must be zero-sized")
23            .to_compile_error()
24            .into();
25    }
26
27    // Get the struct name
28    let name = &input.ident;
29
30    // Get the `monetary` crate
31    let found_crate = crate_name("monetary").expect("Failed to find the `monetary` crate");
32
33    // Prepare the list of derives
34    let mut derives = vec![
35        quote! { Clone },
36        quote! { Debug },
37        quote! { PartialEq },
38        quote! { Eq },
39        quote! { PartialOrd },
40        quote! { Ord },
41        quote! { Copy },
42        quote! { Default },
43    ];
44
45    let mut attributes = vec![];
46
47    // Check for features and conditionally add derives
48    #[cfg(feature = "serde")]
49    match &found_crate {
50        FoundCrate::Itself => {
51            derives.push(quote! { crate::__derive_import::serde::Serialize });
52            derives.push(quote! { crate::__derive_import::serde::Deserialize });
53            attributes.push(quote! { #[serde(crate = "crate::__derive_import::serde")] });
54        }
55        FoundCrate::Name(crate_name) => {
56            let ident = Ident::new(crate_name, Span::call_site());
57            derives.push(quote! { #ident::__derive_import::serde::Serialize });
58            derives.push(quote! { #ident::__derive_import::serde::Deserialize });
59            let serde_crate = format!("::{}::__derive_import::serde", crate_name);
60            attributes.push(quote! { #[serde(crate = #serde_crate)] });
61        }
62    }
63
64    #[cfg(feature = "schemars")]
65    match &found_crate {
66        FoundCrate::Itself => {
67            derives.push(quote! { crate::__derive_import::schemars::JsonSchema });
68            attributes.push(quote! { #[schemars(crate = "crate::__derive_import::schemars")] });
69        }
70        FoundCrate::Name(crate_name) => {
71            let ident = Ident::new(crate_name, Span::call_site());
72            derives.push(quote! { #ident::__derive_import::schemars::JsonSchema });
73            let schemars_crate = format!("::{}::__derive_import::schemars", crate_name);
74            attributes.push(quote! { #[schemars(crate = #schemars_crate)] });
75        }
76    }
77
78    // Combine all derives into one attribute
79    let derives = quote! { #[derive(#(#derives),*)] };
80    let attributes = quote! { #(#attributes)* };
81
82    // Generate the Denomination trait implementation
83    let trait_impl = match found_crate {
84        FoundCrate::Itself => quote! {
85            unsafe impl crate::Denomination for #name {}
86        },
87        FoundCrate::Name(crate_name) => {
88            let ident = Ident::new(&crate_name, Span::call_site());
89            quote! {
90                unsafe impl #ident::Denomination for #name {}
91            }
92        }
93    };
94
95    // Combine the original struct definition with the derives and trait implementation
96    let expanded = quote! {
97        #derives
98        #attributes
99        #input
100        #trait_impl
101    };
102
103    // Convert the generated code back into a TokenStream
104    TokenStream::from(expanded)
105}