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 = &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
133fn 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 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
367enum 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
402enum RequestBody<'a> {
404 Form(&'a Box<syn::Pat>),
405 Json(&'a Box<syn::Pat>),
406}
407
408#[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#[derive(Debug, FromMeta)]
423struct Request {
424 pub path: String,
425 #[darling(default)]
426 pub deserialize: Option<String>,
427}
428
429#[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 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 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}