1extern crate proc_macro;
2
3use crate::RequestBody::{Form, Json};
4use darling::ast::NestedMeta;
5use darling::{Error, FromMeta};
6use proc_macro::TokenStream;
7use proc_macro_error::{abort, proc_macro_error};
8use quote::quote;
9use syn::spanned::Spanned;
10use syn::{parse_macro_input, FnArg, TraitItemFn};
11
12#[proc_macro_error]
27#[proc_macro_attribute]
28pub fn client(args: TokenStream, input: TokenStream) -> TokenStream {
29 let args = match NestedMeta::parse_meta_list(args.into()) {
30 Ok(v) => v,
31 Err(e) => {
32 return TokenStream::from(Error::from(e).write_errors());
33 }
34 };
35 let input = parse_macro_input!(input as syn::ItemTrait);
36 let args: ClientArgs = match ClientArgs::from_list(&args) {
37 Ok(v) => v,
38 Err(e) => return TokenStream::from(e.write_errors()),
39 };
40
41 let reqwest_client_builder = match args.client_builder {
42 Some(builder) => {
43 let builder_token: proc_macro2::TokenStream = builder.parse().unwrap();
44 quote! {
45 #builder_token().await?
46 }
47 }
48 None => quote! {
49 ::feign::re_exports::reqwest::ClientBuilder::new().build()?
50 },
51 };
52
53 let vis = &input.vis;
54 let name = &input.ident;
55 let base_host = &match args.host {
56 None => String::from(""),
57 Some(value) => value,
58 };
59 let base_path = &args.path;
60
61 let methods = input
62 .items
63 .iter()
64 .filter_map(|item| match item {
65 syn::TraitItem::Fn(m) => Some(m),
66 _ => None,
67 })
68 .map(|m| gen_method(m, args.before_send.as_ref(), &reqwest_client_builder));
69
70 let builder_name: proc_macro2::TokenStream =
71 format!("{}Builder", quote! {#name}).parse().unwrap();
72
73 let tokens = quote! {
74
75 #[derive(Debug, Clone)]
76 #vis struct #name {
77 host: String,
78 path: String,
79 }
80
81 impl #name {
82
83 pub fn new() -> Self {
84 Self{
85 host: String::from(#base_host),
86 path: String::from(#base_path),
87 }
88 }
89
90 pub fn new_with_builder(host: String) -> Self {
91 Self{
92 host: host,
93 path: String::from(#base_path),
94 }
95 }
96
97 pub fn builder() -> #builder_name {
98 #builder_name::new()
99 }
100
101 #(#methods)*
102 }
103
104 #vis struct #builder_name {
105 host: String,
106 }
107
108 impl #builder_name {
109
110 pub fn new() -> Self {
111 Self{
112 host: String::from(#base_host),
113 }
114 }
115
116 pub fn build(&self) -> #name {
117 #name::new_with_builder(self.host.clone())
118 }
119
120 pub fn set_host(mut self, host: String) -> Self {
121 self.host = host;
122 self
123 }
124
125 }
126
127 };
128
129 tokens.into()
130}
131
132fn gen_method(
134 method: &TraitItemFn,
135 before_send: Option<&String>,
136 reqwest_client_builder: &proc_macro2::TokenStream,
137) -> proc_macro2::TokenStream {
138 if method.sig.asyncness.is_none() {
139 abort!(
140 &method.sig.span(),
141 "Non-asynchronous calls are not currently supported"
142 )
143 }
144
145 let name = &method.sig.ident;
146 let inputs = &method.sig.inputs;
147 let output = &method.sig.output;
148 let attr = method.attrs.iter().next();
149 let http_method_ident = match attr.map(|a| a.path().get_ident()).flatten() {
150 Some(ident) => ident,
151 None => {
152 abort!(&method.span(), "Expects an http method")
153 }
154 };
155
156 let _http_method = if let Some(m) = http_method_from_ident(http_method_ident) {
157 m
158 } else {
159 abort!(
160 &http_method_ident.span(),
161 "Expect one of get, post, put, patch, delete, head."
162 )
163 };
164
165 let _http_method_token = http_method_to_token(_http_method);
166
167 let request: Request = match Request::from_meta(&attr.unwrap().meta) {
168 Ok(v) => v,
169 Err(err) => return TokenStream::from(err.write_errors()).into(),
170 };
171
172 let req_path = &request.path;
173
174 let mut path_variables = Vec::new();
175 let mut querys = Vec::new();
176 let mut body = None;
177 let mut headers = None;
178
179 match inputs.first() {
180 Some(FnArg::Receiver(_)) => {}
181 _ => abort!(&method.sig.span(), "first arg must be &self"),
182 };
183
184 inputs
185 .iter()
186 .filter_map(|fn_arg| match fn_arg {
187 FnArg::Receiver(_) => None,
188 FnArg::Typed(ty) => Some((ty, &ty.attrs.first()?.path().segments.first()?.ident)),
189 })
190 .for_each(|(ty, p)| match &*p.to_string() {
191 "path" => path_variables.push(&ty.pat),
192 "query" => querys.push(&ty.pat),
193 "json" => match body {
194 None => body = Some(Json(&ty.pat)),
195 _ => abort!(&ty.span(), "json or form only once"),
196 },
197 "form" => match body {
198 None => body = Some(Form(&ty.pat)),
199 _ => abort!(&ty.span(), "json or form only once"),
200 },
201 "headers" => match headers {
202 None => headers = Some(&ty.pat),
203 _ => abort!(&ty.span(), "json or form only once"),
204 },
205 other => abort!(
206 &ty.span(),
207 format!("not allowed param type : {}", other).as_str()
208 ),
209 });
210
211 let path_variables = if path_variables.is_empty() {
212 quote! {}
213 } else {
214 let mut stream = proc_macro2::TokenStream::new();
215 for pv in path_variables {
216 let id = format!("<{}>", quote! {#pv});
217 stream.extend(quote! {
218 .replace(#id, format!("{}", #pv).as_str())
219 });
220 }
221 stream
222 };
223
224 let query = if querys.is_empty() {
225 quote! {}
226 } else {
227 quote! {
228 .query(&[#(#querys),*])
229 }
230 };
231
232 let req_body = match body {
233 None => quote! {},
234 Some(Form(form)) => quote! {
235 .form(#form)
236 },
237 Some(Json(json)) => quote! {
238 .json(#json)
239 },
240 };
241
242 let request_builder_body = match body {
243 None => quote! {
244 feign::RequestBody::None
245 },
246 Some(Form(form)) => quote! {
247 feign::RequestBody::Json(::feign::re_exports::serde_json::to_value(#form)?)
248 },
249 Some(Json(json)) => quote! {
250 feign::RequestBody::Json(::feign::re_exports::serde_json::to_value(#json)?)
251 },
252 };
253
254 let header_mut: proc_macro2::TokenStream = match headers {
255 None => quote! {},
256 Some(_) => quote! {mut},
257 };
258
259 let headers_point = match headers {
260 None => quote! {None},
261 Some(headers) => quote! {Some(#headers)},
262 };
263
264 let headers = match headers {
265 None => quote! {},
266 Some(headers) => quote! {
267 for header in #headers.clone() {
268 req = req.header(header.0,header.1);
269 }
270 },
271 };
272
273 let params = quote! {
274 #query
275 #req_body
276 };
277
278 let inputs = inputs
279 .iter()
280 .filter_map(|fn_arg| match fn_arg {
281 FnArg::Receiver(_) => None,
282 FnArg::Typed(a) => {
283 let mut a = a.clone();
284 a.attrs.clear();
285 Some(FnArg::Typed(a))
286 }
287 })
288 .collect::<syn::punctuated::Punctuated<_, syn::Token![,]>>();
289
290 let before_send_builder = match before_send {
291 Some(builder) => {
292 let builder_token: proc_macro2::TokenStream = builder.clone().parse().unwrap();
293 quote! {
294 #builder_token(
295 req,
296 #_http_method_token,
297 self.host.clone(),
298 self.path.clone(),
299 request_path.clone(),
300 #request_builder_body,
301 #headers_point,
302 ).await?
303 }
304 }
305 None => quote! {
306 req
307 },
308 };
309
310 let deserialize = match request.deserialize {
311 None => quote! {::feign::re_exports::serde_json::from_str(text.as_str())},
312 Some(deserialize) => {
313 let builder_token: proc_macro2::TokenStream = deserialize.parse().unwrap();
314 quote! {#builder_token(text).await}
315 }
316 };
317
318 quote! {
319 pub async fn #name(&self, #inputs) #output {
320 let request_path = String::from(#req_path)#path_variables;
321 let url = format!("{}{}{}", self.host, self.path, request_path);
322 let #header_mut req = #reqwest_client_builder
323 .#http_method_ident(url.as_str())
324 #params;
325 #headers;
326 let req = #before_send_builder;
327 let text = req
328 .send()
329 .await?
330 .error_for_status()?
331 .text()
332 .await?;
333 Ok(#deserialize?)
334 }
335 }
336}
337
338enum HttpMethod {
340 Get,
341 Post,
342 Put,
343 Patch,
344 Delete,
345 Head,
346}
347
348fn http_method_from_ident(ident: &syn::Ident) -> Option<HttpMethod> {
349 Some(match &*ident.to_string() {
350 "get" => HttpMethod::Get,
351 "post" => HttpMethod::Post,
352 "put" => HttpMethod::Put,
353 "patch" => HttpMethod::Patch,
354 "delete" => HttpMethod::Delete,
355 "head" => HttpMethod::Head,
356 _ => return None,
357 })
358}
359
360fn http_method_to_token(method: HttpMethod) -> proc_macro2::TokenStream {
361 match method {
362 HttpMethod::Get => "feign::HttpMethod::Get",
363 HttpMethod::Post => "feign::HttpMethod::Post",
364 HttpMethod::Put => "feign::HttpMethod::Put",
365 HttpMethod::Patch => "feign::HttpMethod::Patch",
366 HttpMethod::Delete => "feign::HttpMethod::Delete",
367 HttpMethod::Head => "feign::HttpMethod::Head",
368 }
369 .parse()
370 .unwrap()
371}
372
373enum RequestBody<'a> {
375 Form(&'a Box<syn::Pat>),
376 Json(&'a Box<syn::Pat>),
377}
378
379#[derive(Debug, FromMeta)]
381struct ClientArgs {
382 #[darling(default)]
383 pub host: Option<String>,
384 pub path: String,
385 #[darling(default)]
386 pub client_builder: Option<String>,
387 #[darling(default)]
388 pub before_send: Option<String>,
389}
390
391#[derive(Debug, FromMeta)]
393struct Request {
394 pub path: String,
395 #[darling(default)]
396 pub deserialize: Option<String>,
397}