Skip to main content

api_dzengi_rs_macro/
lib.rs

1use heck::ToLowerCamelCase;
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use syn::{
5    Attribute, DeriveInput, Expr, Lit, Meta, Token, parse_macro_input, punctuated::Punctuated,
6};
7
8#[proc_macro_derive(RequestMethods)]
9pub fn derive_with_methods(input: TokenStream) -> TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11    let struct_name = &input.ident;
12
13    let fields = if let syn::Data::Struct(syn::DataStruct {
14        fields: syn::Fields::Named(syn::FieldsNamed { named, .. }),
15        ..
16    }) = input.data
17    {
18        named
19    } else {
20        panic!("RequestMethods works only on structs with named fields");
21    };
22
23    let args = fields.iter().filter_map(|field| {
24        let field_name = field.ident.as_ref().unwrap();
25        let field_type = &field.ty;
26        if is_option_type(&field.ty) {
27            None
28        } else {
29            Some(quote! {
30                #field_name: #field_type
31            })
32        }
33    });
34
35    let create = fields.iter().map(|field| {
36        let field_name = field.ident.as_ref().unwrap();
37        if is_option_type(&field.ty) {
38            quote! {#field_name: None}
39        } else {
40            quote! {#field_name}
41        }
42    });
43
44    let methods = fields.iter().map(|field| {
45        let field_name = field.ident.as_ref().unwrap();
46        let method_name = format_ident!("with_{}", field_name);
47        let field_type = &field.ty;
48        quote! {
49            pub fn #method_name(mut self, #field_name: #field_type) -> Self {
50                self.#field_name = #field_name;
51                self
52            }
53        }
54    });
55
56    let open_query = fields.iter().map(|field| {
57        let field_name = field.ident.as_ref().unwrap();
58        let field_key = extract_serde_rename(&field.attrs)
59            .unwrap_or_else(|| field_name.to_string().to_lower_camel_case());
60        if is_option_type(&field.ty) {
61            quote! {
62                query.add_option(#field_key, self.#field_name);
63            }
64        } else {
65            quote! {
66                query.add(#field_key, self.#field_name);
67            }
68        }
69    });
70
71    let expanded = quote! {
72        impl #struct_name {
73            pub fn new(#(#args),*) -> Self {
74                Self {
75                    #(#create),*
76                }
77            }
78
79            #(#methods)*
80
81            pub fn fill_query<const N: usize>(self, query: &mut Query<N>) {
82                #(#open_query)*
83            }
84        }
85    };
86
87    TokenStream::from(expanded)
88}
89
90fn is_option_type(ty: &syn::Type) -> bool {
91    if let syn::Type::Path(type_path) = ty {
92        if let Some(segment) = type_path.path.segments.last() {
93            return segment.ident == "Option";
94        }
95    }
96    false
97}
98
99fn extract_serde_rename(attrs: &[Attribute]) -> Option<String> {
100    for attr in attrs {
101        if !attr.path().is_ident("serde") {
102            continue;
103        }
104
105        let meta = attr.parse_args::<Meta>().ok()?;
106
107        match meta {
108            Meta::NameValue(name_value) if name_value.path.is_ident("rename") => {
109                if let Expr::Lit(expr_lit) = name_value.value {
110                    if let Lit::Str(lit_str) = expr_lit.lit {
111                        return Some(lit_str.value());
112                    }
113                }
114            }
115            Meta::List(meta_list) if meta_list.path.is_ident("rename") => {
116                for nested in meta_list
117                    .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
118                    .ok()?
119                {
120                    if let Meta::NameValue(nv) = nested {
121                        if nv.path.is_ident("serialize") || nv.path.is_ident("deserialize") {
122                            if let Expr::Lit(expr_lit) = nv.value {
123                                if let Lit::Str(lit_str) = expr_lit.lit {
124                                    return Some(lit_str.value());
125                                }
126                            }
127                        }
128                    }
129                }
130            }
131            _ => {}
132        }
133    }
134    None
135}