feign_macros/
lib.rs

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