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)]
76 #vis struct #name {
77 host: std::sync::Arc<dyn feign::Host>,
78 path: String,
79 }
80
81 impl #name {
82
83 pub fn new() -> Self {
84 Self{
85 host: std::sync::Arc::new(String::from(#base_host)),
86 path: String::from(#base_path),
87 }
88 }
89
90 pub fn new_with_builder(host: std::sync::Arc<dyn feign::Host>) -> Self {
91 Self{
92 host,
93 path: String::from(#base_path).into(),
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: std::sync::Arc<dyn feign::Host>,
106 }
107
108 impl #builder_name {
109
110 pub fn new() -> Self {
111 Self{
112 host: std::sync::Arc::new(String::from(#base_host)),
113 }
114 }
115
116 pub fn build(self) -> #name {
117 #name::new_with_builder(self.host)
118 }
119
120 pub fn set_host(mut self, host: impl ::feign::Host) -> Self {
121 self.host = std::sync::Arc::new(host);
122 self
123 }
124
125 pub fn set_host_arc(mut self, host: std::sync::Arc<dyn ::feign::Host>) -> Self {
126 self.host = host;
127 self
128 }
129
130 }
131
132 };
133
134 tokens.into()
135}
136
137fn gen_method(
139 method: &TraitItemFn,
140 before_send: Option<&String>,
141 reqwest_client_builder: &proc_macro2::TokenStream,
142) -> proc_macro2::TokenStream {
143 if method.sig.asyncness.is_none() {
144 abort!(
145 &method.sig.span(),
146 "Non-asynchronous calls are not currently supported"
147 )
148 }
149
150 let name = &method.sig.ident;
151 let inputs = &method.sig.inputs;
152 let output = &method.sig.output;
153 let attr = method.attrs.iter().next();
154 let http_method_ident = match attr.map(|a| a.path().get_ident()).flatten() {
155 Some(ident) => ident,
156 None => {
157 abort!(&method.span(), "Expects an http method")
158 }
159 };
160
161 let _http_method = if let Some(m) = http_method_from_ident(http_method_ident) {
162 m
163 } else {
164 abort!(
165 &http_method_ident.span(),
166 "Expect one of get, post, put, patch, delete, head."
167 )
168 };
169
170 let _http_method_token = http_method_to_token(_http_method);
171
172 let request: Request = match Request::from_meta(&attr.unwrap().meta) {
173 Ok(v) => v,
174 Err(err) => return TokenStream::from(err.write_errors()).into(),
175 };
176
177 let req_path = &request.path;
178
179 let mut path_variables = Vec::new();
180 let mut querys = Vec::new();
181 let mut body = None;
182 let mut headers = None;
183 let mut args = None;
184
185 match inputs.first() {
186 Some(FnArg::Receiver(_)) => {}
187 _ => abort!(&method.sig.span(), "first arg must be &self"),
188 };
189
190 inputs
191 .iter()
192 .filter_map(|fn_arg| match fn_arg {
193 FnArg::Receiver(_) => None,
194 FnArg::Typed(ty) => Some((ty, &ty.attrs.first()?.path().segments.first()?.ident)),
195 })
196 .for_each(|(ty, p)| match &*p.to_string() {
197 "path" => path_variables.push(&ty.pat),
198 "query" => querys.push(&ty.pat),
199 "json" => match body {
200 None => body = Some(Json(&ty.pat)),
201 _ => abort!(&ty.span(), "json or form only once"),
202 },
203 "form" => match body {
204 None => body = Some(Form(&ty.pat)),
205 _ => abort!(&ty.span(), "json or form only once"),
206 },
207 "headers" => match headers {
208 None => headers = Some(&ty.pat),
209 _ => abort!(&ty.span(), "headers only once"),
210 },
211 "args" => match args {
212 None => args = Some(&ty.pat),
213 _ => abort!(&ty.span(), "args only once"),
214 },
215 other => abort!(
216 &ty.span(),
217 format!("not allowed param type : {}", other).as_str()
218 ),
219 });
220
221 let path_variables = if path_variables.is_empty() {
222 quote! {}
223 } else {
224 let mut stream = proc_macro2::TokenStream::new();
225 for pv in path_variables {
226 let id = format!("<{}>", quote! {#pv});
227 stream.extend(quote! {
228 .replace(#id, format!("{}", #pv).as_str())
229 });
230 }
231 stream
232 };
233
234 let mut query = if querys.is_empty() {
235 quote! {}
236 } else {
237 quote! {
238 req = req.query(&[#(#querys),*]);
239 }
240 };
241
242 let (mut req_body, mut req_body_enum) = match body {
243 None => (quote! {}, quote! {feign::RequestBody::<()>::None}),
244 Some(Form(form)) => (
245 quote! {
246 req = req.form(#form);
247 },
248 quote! {feign::RequestBody::Form(#form)},
249 ),
250 Some(Json(json)) => (
251 quote! {
252 req = req.json(#json);
253 },
254 quote! {feign::RequestBody::Json(#json)},
255 ),
256 };
257
258 let mut headers = match headers {
259 None => quote! {},
260 Some(headers) => quote! {
261 for header in #headers {
262 req = req.header(header.0,header.1);
263 }
264 },
265 };
266
267 let mut args_path = quote! {};
268 if let Some(args) = args {
269 args_path = quote! {
270 for path in #args.path() {
271 request_path = request_path.replace(path.0, path.1.as_str());
272 }
273 };
274 query = quote! {
275 if let Some(query) = #args.query() {
276 req = req.query(&query);
277 }
278 };
279 if body.is_some() {
281 req_body = quote! {
282 #req_body
283 match #args.body() {
284 feign::RequestBody::None => {},
285 _ => {
286 return Err(feign::re_exports::anyhow::anyhow!("json or form can only once"));
287 },
288 }
289 };
290 } else {
291 req_body = quote! {
292 let req_body = #args.body();
293 match &req_body {
294 feign::RequestBody::None => {},
295 feign::RequestBody::Form(form) => {
296 req = req.form(form);
297 },
298 feign::RequestBody::Json(json) => {
299 req = req.json(json);
300 },
301 }
302 };
303 req_body_enum = quote! {req_body};
304 }
305 headers = quote! {
306 #headers
307 if let Some(headers) = #args.headers() {
308 for header in headers {
309 req = req.header(header.0, header.1);
310 }
311 }
312 };
313 };
314
315 let inputs = inputs
316 .iter()
317 .filter_map(|fn_arg| match fn_arg {
318 FnArg::Receiver(_) => None,
319 FnArg::Typed(a) => {
320 let mut a = a.clone();
321 a.attrs.clear();
322 Some(FnArg::Typed(a))
323 }
324 })
325 .collect::<syn::punctuated::Punctuated<_, syn::Token![,]>>();
326
327 let before_send_builder = match before_send {
328 Some(builder) => {
329 let builder_token: proc_macro2::TokenStream = builder.clone().parse().unwrap();
330 quote! {
331 let req = #builder_token(
332 req,
333 #req_body_enum,
334 ).await?;
335 }
336 }
337 None => quote! {},
338 };
339
340 let deserialize = match request.deserialize {
341 None => quote! {::feign::re_exports::serde_json::from_str(text.as_str())},
342 Some(deserialize) => {
343 let builder_token: proc_macro2::TokenStream = deserialize.parse().unwrap();
344 quote! {#builder_token(text).await}
345 }
346 };
347
348 quote! {
349 pub async fn #name(&self, #inputs) #output {
350 let mut request_path = String::from(#req_path)#path_variables;
351 #args_path
352 let url = format!("{}{}{}", self.host, self.path, request_path);
353 let mut req = #reqwest_client_builder
354 .#http_method_ident(url.as_str());
355 #query
356 #req_body
357 #headers
358 #before_send_builder
359 let text = req
360 .send()
361 .await?
362 .error_for_status()?
363 .text()
364 .await?;
365 Ok(#deserialize?)
366 }
367 }
368}
369
370enum HttpMethod {
372 Get,
373 Post,
374 Put,
375 Patch,
376 Delete,
377 Head,
378}
379
380fn http_method_from_ident(ident: &syn::Ident) -> Option<HttpMethod> {
381 Some(match &*ident.to_string() {
382 "get" => HttpMethod::Get,
383 "post" => HttpMethod::Post,
384 "put" => HttpMethod::Put,
385 "patch" => HttpMethod::Patch,
386 "delete" => HttpMethod::Delete,
387 "head" => HttpMethod::Head,
388 _ => return None,
389 })
390}
391
392fn http_method_to_token(method: HttpMethod) -> proc_macro2::TokenStream {
393 match method {
394 HttpMethod::Get => "feign::HttpMethod::Get",
395 HttpMethod::Post => "feign::HttpMethod::Post",
396 HttpMethod::Put => "feign::HttpMethod::Put",
397 HttpMethod::Patch => "feign::HttpMethod::Patch",
398 HttpMethod::Delete => "feign::HttpMethod::Delete",
399 HttpMethod::Head => "feign::HttpMethod::Head",
400 }
401 .parse()
402 .unwrap()
403}
404
405enum RequestBody<'a> {
407 Form(&'a Box<syn::Pat>),
408 Json(&'a Box<syn::Pat>),
409}
410
411#[derive(Debug, FromMeta)]
413struct ClientArgs {
414 #[darling(default)]
415 pub host: Option<String>,
416 pub path: String,
417 #[darling(default)]
418 pub client_builder: Option<String>,
419 #[darling(default)]
420 pub before_send: Option<String>,
421}
422
423#[derive(Debug, FromMeta)]
425struct Request {
426 pub path: String,
427 #[darling(default)]
428 pub deserialize: Option<String>,
429}
430
431#[proc_macro_error]
455#[proc_macro_derive(
456 Args,
457 attributes(feigen_path, feigen_query, feigen_json, feigen_form, feigen_headers)
458)]
459pub fn derive_args(input: TokenStream) -> TokenStream {
460 let input = parse_macro_input!(input as syn::DeriveInput);
461 let name = &input.ident;
462
463 let fields = match &input.data {
464 syn::Data::Struct(data) => &data.fields,
465 _ => abort!(
466 &input.ident.span(),
467 "Args derive macro only supports structs"
468 ),
469 };
470
471 let mut path_fields: Vec<(&syn::Ident, &syn::Type)> = Vec::new();
472 let mut query_fields: Vec<(&syn::Ident, &syn::Type)> = Vec::new();
473 let mut json_field: Option<(&syn::Ident, &syn::Type)> = None;
474 let mut form_field: Option<(&syn::Ident, &syn::Type)> = None;
475 let mut headers_field: Option<(&syn::Ident, &syn::Type)> = None;
476
477 for field in fields.iter() {
478 let field_name = field.ident.as_ref().unwrap();
479 let field_type = &field.ty;
480
481 let mut has_path = false;
482 let mut has_query = false;
483 let mut has_json = false;
484 let mut has_form = false;
485 let mut has_headers = false;
486
487 for attr in &field.attrs {
488 if let syn::Meta::Path(path) = &attr.meta {
489 if let Some(ident) = path.get_ident() {
490 match ident.to_string().as_str() {
491 "feigen_path" => has_path = true,
492 "feigen_query" => has_query = true,
493 "feigen_json" => has_json = true,
494 "feigen_form" => has_form = true,
495 "feigen_headers" => has_headers = true,
496 _ => {}
497 }
498 }
499 }
500 }
501
502 if has_path {
503 path_fields.push((field_name, field_type));
504 } else if has_query {
505 query_fields.push((field_name, field_type));
506 } else if has_json {
507 match json_field {
508 None => json_field = Some((field_name, field_type)),
509 _ => abort!(&field.span(), "json only once"),
510 }
511 } else if has_form {
512 match form_field {
513 None => form_field = Some((field_name, field_type)),
514 _ => abort!(&field.span(), "form only once"),
515 }
516 } else if has_headers {
517 match headers_field {
518 None => headers_field = Some((field_name, field_type)),
519 _ => abort!(&field.span(), "headers only once"),
520 }
521 }
522 }
523
524 if json_field.is_some() && form_field.is_some() {
525 abort!(&fields.span(), "json or form only once");
526 }
527
528 let path = if path_fields.is_empty() {
530 quote! {}
531 } else {
532 let path_pairs: Vec<_> = path_fields
533 .iter()
534 .map(|(field_name, _)| {
535 let id = format!("<{}>", field_name);
536 quote! {
537 (#id, format!("{}", self.#field_name))
538 }
539 })
540 .collect();
541 quote! {
542 vec![#(#path_pairs),*]
543 }
544 };
545
546 let query = if query_fields.is_empty() {
548 quote! {None}
549 } else {
550 let query_pairs: Vec<_> = query_fields
551 .iter()
552 .map(|(field_name, _)| {
553 quote! {
554 (stringify!(#field_name), format!("{}", self.#field_name))
555 }
556 })
557 .collect();
558 quote! {
559 Some(vec![#(#query_pairs),*])
560 }
561 };
562
563 let (body, body_type) = match (form_field, json_field) {
564 (Some((field_name, ty)), None) => (
565 quote! {
566 feign::RequestBody::Form(&self.#field_name)
567 },
568 quote! {feign::RequestBody<&#ty>},
569 ),
570 (None, Some((field_name, ty))) => (
571 quote! {
572 feign::RequestBody::Json(&self.#field_name)
573 },
574 quote! {feign::RequestBody<&#ty>},
575 ),
576 _ => (
577 quote! {feign::RequestBody::<()>::None},
578 quote! {feign::RequestBody<()>},
579 ),
580 };
581
582 let (headers, headers_type) = match headers_field {
583 None => (quote! {None}, quote! {Option<()>}),
584 Some((field_name, ty)) => (
585 quote! {
586 Some(&self.#field_name)
587 },
588 quote! {Option<&#ty>},
589 ),
590 };
591
592 let expanded = quote! {
593 impl #name {
594 fn path(&self) -> Vec<(&'static str, String)> {
595 #path
596 }
597
598 fn query(&self) -> Option<Vec<(&'static str, String)>> {
599 #query
600 }
601
602 fn body(&self) -> #body_type {
603 #body
604 }
605
606 fn headers(&self) -> #headers_type {
607 #headers
608 }
609 }
610 };
611
612 TokenStream::from(expanded)
613}