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
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{parse_macro_input, Ident, ItemStruct};

#[proc_macro_attribute]
/// This macro:
/// - Ensures that the input item is a Zero-sized struct
/// - Derives Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Default
/// - Derives Serialize, Deserialize if the feature "serde" is enabled
/// - Derives JsonSchema if the feature "schemars" is enabled
/// - Adds the `unsafe` Denomination trait to the input item
pub fn denom(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // Parse the input token stream into a syntax tree
    let input = parse_macro_input!(item as ItemStruct);

    // Ensure the struct is zero-sized
    if !input.fields.is_empty() {
        return syn::Error::new_spanned(input, "Struct must be zero-sized")
            .to_compile_error()
            .into();
    }

    // Get the struct name
    let name = &input.ident;

    // Prepare the list of derives
    let mut derives = vec![
        quote! { Clone },
        quote! { Debug },
        quote! { PartialEq },
        quote! { Eq },
        quote! { PartialOrd },
        quote! { Ord },
        quote! { Copy },
        quote! { Default },
    ];

    // Check for features and conditionally add derives
    #[cfg(feature = "serde")]
    {
        derives.push(quote! { ::serde::Serialize });
        derives.push(quote! { ::serde::Deserialize });
    }

    #[cfg(feature = "schemars")]
    {
        derives.push(quote! { ::schemars::JsonSchema });
    }

    // Combine all derives into one attribute
    let derives = quote! { #[derive(#(#derives),*)] };

    // Get the `monetary` crate
    let found_crate = crate_name("monetary").expect("Failed to find the `monetary` crate");

    // Generate the Denomination trait implementation
    let trait_impl = match found_crate {
        FoundCrate::Itself => quote! {
            unsafe impl crate::Denomination for #name {}
        },
        FoundCrate::Name(crate_name) => {
            let ident = Ident::new(&crate_name, Span::call_site());
            quote! {
                unsafe impl #ident::Denomination for #name {}
            }
        }
    };

    // Combine the original struct definition with the derives and trait implementation
    let expanded = quote! {
        #derives
        #input
        #trait_impl
    };

    // Convert the generated code back into a TokenStream
    TokenStream::from(expanded)
}