Skip to main content

xrpl_rust_macros/
lib.rs

1#![no_std]
2
3extern crate alloc;
4extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{Data, DeriveInput, Fields, Type, parse_macro_input};
9
10/// Derive macro to implement `ValidateCurrencies` trait for structs.
11/// This macro checks for fields of type `Amount`, `XRPAmount`, `IssuedCurrencyAmount`, `Currency`, `XRP`, or `IssuedCurrency`.
12/// It generates a `validate_currencies` method that validates these values.
13#[proc_macro_derive(ValidateCurrencies)]
14pub fn derive_validate_currencies(input: TokenStream) -> TokenStream {
15    let input = parse_macro_input!(input as DeriveInput);
16    let name = input.ident;
17
18    let fields = match input.data {
19        Data::Struct(data_struct) => match data_struct.fields {
20            Fields::Named(fields_named) => fields_named.named,
21            _ => {
22                return syn::Error::new_spanned(name, "Only named fields supported")
23                    .to_compile_error()
24                    .into();
25            }
26        },
27        _ => {
28            return syn::Error::new_spanned(name, "Only structs are supported")
29                .to_compile_error()
30                .into();
31        }
32    };
33
34    let amount_field_validations = fields.iter().filter_map(|field| {
35        let ident = &field.ident;
36        match &field.ty {
37            // Handle Option<T> where T is one of the valid types
38            Type::Path(type_path) => {
39                use alloc::string::ToString;
40                let segments = &type_path.path.segments;
41                if segments.len() == 1 && segments[0].ident == "Option" {
42                    // Extract T from Option<T>
43                    if let syn::PathArguments::AngleBracketed(angle_bracketed) =
44                        &segments[0].arguments
45                    {
46                        if let Some(syn::GenericArgument::Type(Type::Path(inner_type_path))) =
47                            angle_bracketed.args.first()
48                        {
49                            let inner_ident = &inner_type_path.path.segments.last().unwrap().ident;
50                            if [
51                                "Amount",
52                                "XRPAmount",
53                                "IssuedCurrencyAmount",
54                                "Currency",
55                                "XRP",
56                                "IssuedCurrency",
57                            ]
58                            .contains(&inner_ident.to_string().as_str())
59                            {
60                                return Some(quote! {
61                                    if let Some(x) = &self.#ident {
62                                        x.validate()?;
63                                    }
64                                });
65                            }
66                        }
67                    }
68                }
69
70                // Handle direct fields: Amount, XRPAmount, etc.
71                let type_ident = &segments.last().unwrap().ident;
72                if [
73                    "Amount",
74                    "XRPAmount",
75                    "IssuedCurrencyAmount",
76                    "Currency",
77                    "XRP",
78                    "IssuedCurrency",
79                ]
80                .contains(&type_ident.to_string().as_str())
81                {
82                    return Some(quote! {
83                        self.#ident.validate()?;
84                    });
85                }
86
87                None
88            }
89            _ => None,
90        }
91    });
92
93    let generics = input.generics;
94    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
95
96    let expanded = quote! {
97        impl #impl_generics ValidateCurrencies for #name #ty_generics #where_clause {
98            fn validate_currencies(&self) -> crate::models::XRPLModelResult<()> {
99                #(#amount_field_validations)*
100
101                Ok(())
102            }
103        }
104    };
105
106    TokenStream::from(expanded)
107}