api_dzengi_rs_macro/
lib.rs1use 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}