smarty_rust_proc_macro/
lib.rs1#![allow(clippy::manual_unwrap_or_default)]
2
3extern crate darling;
4extern crate proc_macro;
5
6use darling::{export::NestedMeta, Error, FromMeta};
7use proc_macro::TokenStream;
8use quote::quote;
9use syn::Path;
10
11#[derive(Default, Debug, FromMeta)]
12struct LookupOverride {
13 #[darling(default)]
14 lookup_method: Option<Path>,
15 #[darling(default)]
16 batch_method: Option<Path>,
17}
18
19#[derive(Debug, FromMeta, Default)]
20struct LookupStyle {
21 #[darling(default)]
22 batch: bool,
23 #[darling(default)]
24 lookup: bool,
25 #[darling(default)]
26 overrides: LookupOverride,
27}
28
29impl LookupStyle {
30 fn get_lookup_method(&self) -> Path {
31 match self.overrides.lookup_method.clone() {
32 Some(path) => path,
33 None => Path::from_string("Method::GET").expect("Constant failed to parse"),
34 }
35 }
36
37 fn get_batch_method(&self) -> Path {
38 match self.overrides.batch_method.clone() {
39 Some(path) => path,
40 None => Path::from_string("Method::POST").expect("Constant failed to parse"),
41 }
42 }
43}
44
45#[derive(Debug, FromMeta, Default)]
46struct ResultHandler {
47 #[darling(default)]
48 lookup: bool,
49 #[darling(default)]
50 batch: bool,
51}
52
53#[derive(Debug, FromMeta)]
54struct MacroArgs {
55 default_url: String,
56 api_path: String,
57 lookup_style: LookupStyle,
58 lookup_type: Path,
59 result_type: Path,
60 #[darling(default)]
61 multi_result_type: Option<Path>,
62 #[darling(default)]
63 custom_send: bool,
64 #[darling(default)]
65 result_handler: ResultHandler,
66}
67
68#[proc_macro_attribute]
70pub fn smarty_api(args: TokenStream, input: TokenStream) -> TokenStream {
71 let attr_args = match NestedMeta::parse_meta_list(args.into()) {
73 Ok(v) => v,
74 Err(e) => return TokenStream::from(Error::from(e).write_errors()),
75 };
76
77 let mut ast = syn::parse(input).expect("macro input stream should be valid");
78
79 let args = match MacroArgs::from_list(attr_args.as_ref()) {
81 Ok(v) => v,
82 Err(e) => return TokenStream::from(e.write_errors()),
83 };
84
85 impl_smarty_api_macro(&args, &mut ast)
86}
87
88fn impl_smarty_api_macro(attrs: &MacroArgs, ast: &mut syn::DeriveInput) -> TokenStream {
89 let name = &ast.ident;
91
92 let default_url = attrs.default_url.clone();
93 let api_path = attrs.api_path.clone();
94
95 let lookup_type = attrs.lookup_type.clone();
96 let result_type = attrs.result_type.clone();
97 let multi_result_type = match attrs.multi_result_type.clone() {
98 Some(r) => r,
99 None => result_type.clone(),
100 };
101
102 let mut result = quote! {
104 pub struct #name {
105 pub(crate) client: Client
106 }
107
108 impl #name {
109 pub fn new(options: Options) -> Result<Self, SmartyError> {
111 let url = options.url.clone().unwrap_or(#default_url.parse().expect("Parsing Constant should be OK"));
112 Ok(Self {client: Client::new(url, options, #api_path)?})
113 }
114 }
115 };
116
117 let lookup_handler = match attrs.result_handler.lookup {
118 true => {
119 quote! { self.handle_lookup_results(lookup, results); }
120 }
121 false => {
122 quote! {
123 lookup.results = candidates;
124 }
125 }
126 };
127
128 let lookup_method = attrs.lookup_style.get_lookup_method();
129
130 if !attrs.custom_send {
131 if attrs.lookup_style.batch && attrs.lookup_style.lookup {
132 unimplemented!("Only one between batch and lookup allowed for lookup style");
133 } else if attrs.lookup_style.batch {
134 let batch_handler = match attrs.result_handler.batch {
135 true => {
136 quote! { self.handle_batch_results(batch, results); }
137 }
138 false => {
139 quote! {
140 let records = batch.records_mut();
141 for i in 0..records.len() {
142 records[i].results = results[i].clone();
143 }
144 }
145 }
146 };
147
148 let batch_method = attrs.lookup_style.get_batch_method();
149
150 result = quote! {
151 #result
152
153 impl #name {
154 async fn send_lookup(&self, lookup: &mut #lookup_type) -> Result<(), SmartyError> {
158 let mut req = self
159 .client
160 .reqwest_client
161 .request(#lookup_method, self.client.url.clone());
162 req = self.client.build_request(req);
163 req = req.query(&lookup.clone().into_param_array());
164
165 let candidates = send_request::<#result_type>(req).await?;
166
167 #lookup_handler
168
169 Ok(())
170 }
171
172 pub async fn send(&self, batch: &mut Batch<#lookup_type>) -> Result<(), SmartyError> {
176 if batch.is_empty() {
177 return Ok(())
178 }
179
180 if batch.len() == 1 {
181 return self.send_lookup(&mut batch.records_mut()[0]).await
182 }
183
184 let mut req = self.client.reqwest_client.request(#batch_method, self.client.url.clone());
185 req = self.client.build_request(req);
186 req = req.json(batch.records());
187 req = req.header("Content-Type", "application/json");
188
189 let results: #multi_result_type = send_request(req).await?;
190
191 #batch_handler
192
193 Ok(())
194 }
195 }
196 }
197 } else if attrs.lookup_style.lookup {
198 result = quote! {
199 #result
200
201 impl #name {
202 pub async fn send(&self, lookup: &mut #lookup_type) -> Result<(), SmartyError> {
206 let mut req = self
207 .client
208 .reqwest_client
209 .request(#lookup_method, self.client.url.clone());
210 req = self.client.build_request(req);
211 req = req.query(&lookup.clone().into_param_array());
212
213 let candidates = send_request::<#result_type>(req).await?;
214
215 #lookup_handler
216
217 Ok(())
218 }
219 }
220 }
221 } else {
222 unimplemented!("Add batch or lookup to lookup style");
223 }
224 }
225
226 result.into()
227}