safe_vk_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{parse_macro_input, Data, DeriveInput, Fields, ItemFn, LitStr, ReturnType};
4
5#[proc_macro_attribute]
6pub fn auto_ok(_attr: TokenStream, item: TokenStream) -> TokenStream {
7    let mut input_fn = parse_macro_input!(item as ItemFn);
8
9    if input_fn.sig.asyncness.is_some() {
10        if matches!(input_fn.sig.output, ReturnType::Default) {
11            input_fn.sig.output = syn::parse_quote!(-> safe_vk::Result<()>);
12        }
13
14        let ok_stmt: syn::Stmt = syn::parse_quote! {
15            return Ok(());
16        };
17
18        input_fn.block.stmts.push(ok_stmt);
19    }
20
21    let ItemFn {
22        attrs,
23        vis,
24        sig,
25        block,
26    } = input_fn;
27
28    let expanded = quote! {
29        #(#attrs)* #vis #sig {
30            #block
31        }
32    };
33
34    TokenStream::from(expanded)
35}
36
37#[proc_macro_derive(Method, attributes(method_path, optional))]
38pub fn derive_method(input: TokenStream) -> TokenStream {
39    let input = parse_macro_input!(input as DeriveInput);
40    let struct_name = input.ident;
41    let method_struct_name = format_ident!("{}Method", struct_name);
42    let response_struct_name = format_ident!("{}Response", struct_name);
43
44    let is_optional = input
45        .attrs
46        .iter()
47        .any(|attr| attr.path().is_ident("optional"));
48
49    let method_path = input
50        .attrs
51        .iter()
52        .find_map(|attr| {
53            if attr.path().is_ident("method_path") {
54                Some(
55                    attr.parse_args::<LitStr>()
56                        .expect("Expected a string literal"),
57                )
58            } else {
59                None
60            }
61        })
62        .expect("Expected #[method_path = \"..\"] attribute");
63
64    let response_type = if is_optional {
65        quote! { Option<#response_struct_name> }
66    } else {
67        quote! { #response_struct_name }
68    };
69
70    let generated = match &input.data {
71        Data::Struct(data_struct) => match &data_struct.fields {
72            // Handle named fields (normal struct with field names)
73            Fields::Named(fields) => {
74                let field_names = fields.named.iter().map(|f| &f.ident);
75                let field_types = fields.named.iter().map(|f| &f.ty);
76
77                quote! {
78                    pub struct #method_struct_name;
79
80                    #[derive(serde::Deserialize, Debug)]
81                    pub struct #response_struct_name {
82                        // Add `pub` to the field definitions here
83                        #(pub #field_names: #field_types),*
84                    }
85
86                    impl crate::api::Write for crate::api::MethodBuilder<#method_struct_name> {
87                        fn write(&mut self, arg: &[u8]) {
88                            self.query.extend_from_slice(arg);
89                        }
90
91                        fn write_fmt(&mut self, arg: impl std::fmt::Display) {
92                            use std::io::Write;
93                            write!(self.query, "{}", arg).unwrap();
94                        }
95                    }
96
97                    impl IntoFuture for crate::api::MethodBuilder<#method_struct_name> {
98                        type Output = crate::Result<#response_type>;
99                        type IntoFuture = futures_core::future::BoxFuture<'static, crate::Result<#response_type>>;
100
101                        fn into_future(self) -> Self::IntoFuture {
102                            Box::pin(async move {
103                                let response = self.request.post(crate::VK, #method_path, &self.query, {}).await?;
104                                let parsed = parse_response!(response, #response_type)?;
105                                Ok(parsed)
106                            })
107                        }
108                    }
109                }
110            }
111            // Handle unnamed fields (tuple structs)
112            Fields::Unnamed(fields) => {
113                let field_types = fields.unnamed.iter().map(|f| &f.ty).collect::<Vec<_>>();
114
115                // Use the single type in the tuple
116                let field_type = &field_types[0];
117
118                quote! {
119                        pub struct #method_struct_name;
120
121                        impl crate::api::Write for crate::api::MethodBuilder<#method_struct_name> {
122                            fn write(&mut self, arg: &[u8]) {
123                                self.query.extend_from_slice(arg);
124                            }
125
126                            fn write_fmt(&mut self, arg: impl std::fmt::Display) {
127                                use std::io::Write;
128                                write!(self.query, "{}", arg).unwrap();
129                            }
130                        }
131
132                        impl IntoFuture for crate::api::MethodBuilder<#method_struct_name> {
133                            type Output = crate::Result<#field_type>;
134                            type IntoFuture = futures_core::future::BoxFuture<'static, crate::Result<#field_type>>;
135
136                            fn into_future(self) -> Self::IntoFuture {
137                                Box::pin(async move {
138                                    let response = self.request.post(crate::VK, #method_path, &self.query, {}).await?;
139                                    let parsed = parse_response!(response, #field_type)?;
140                                    Ok(parsed)
141                                })
142                            }
143                        }
144
145                }
146            }
147            // Handle unit structs (no fields)
148            Fields::Unit => {
149                quote! {
150                    pub struct #method_struct_name;
151
152                    #[derive(serde::Deserialize, Debug)]
153                    pub struct #response_struct_name;
154
155                    impl crate::api::Write for crate::api::MethodBuilder<#method_struct_name> {
156                        fn write(&mut self, arg: &[u8]) {
157                            self.query.extend_from_slice(arg);
158                        }
159
160                        fn write_fmt(&mut self, arg: impl std::fmt::Display) {
161                            use std::io::Write;
162                            write!(self.query, "{}", arg).unwrap();
163                        }
164                    }
165
166                    impl IntoFuture for crate::api::MethodBuilder<#method_struct_name> {
167                        type Output = crate::Result<#response_type>;
168                        type IntoFuture = futures_core::future::BoxFuture<'static, crate::Result<#response_type>>;
169
170                        fn into_future(self) -> Self::IntoFuture {
171                            Box::pin(async move {
172                                let response = self.request.post(crate::VK, #method_path, &self.query, {}).await?;
173                                let parsed = parse_response!(response, #response_type)?;
174                                Ok(parsed)
175                            })
176                        }
177                    }
178                }
179            }
180        },
181        _ => quote!(),
182    };
183
184    generated.into()
185}