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 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 #(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 Fields::Unnamed(fields) => {
113 let field_types = fields.unnamed.iter().map(|f| &f.ty).collect::<Vec<_>>();
114
115 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 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}