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
184 match inputs.first() {
185 Some(FnArg::Receiver(_)) => {}
186 _ => abort!(&method.sig.span(), "first arg must be &self"),
187 };
188
189 inputs
190 .iter()
191 .filter_map(|fn_arg| match fn_arg {
192 FnArg::Receiver(_) => None,
193 FnArg::Typed(ty) => Some((ty, &ty.attrs.first()?.path().segments.first()?.ident)),
194 })
195 .for_each(|(ty, p)| match &*p.to_string() {
196 "path" => path_variables.push(&ty.pat),
197 "query" => querys.push(&ty.pat),
198 "json" => match body {
199 None => body = Some(Json(&ty.pat)),
200 _ => abort!(&ty.span(), "json or form only once"),
201 },
202 "form" => match body {
203 None => body = Some(Form(&ty.pat)),
204 _ => abort!(&ty.span(), "json or form only once"),
205 },
206 "headers" => match headers {
207 None => headers = Some(&ty.pat),
208 _ => abort!(&ty.span(), "json or form only once"),
209 },
210 other => abort!(
211 &ty.span(),
212 format!("not allowed param type : {}", other).as_str()
213 ),
214 });
215
216 let path_variables = if path_variables.is_empty() {
217 quote! {}
218 } else {
219 let mut stream = proc_macro2::TokenStream::new();
220 for pv in path_variables {
221 let id = format!("<{}>", quote! {#pv});
222 stream.extend(quote! {
223 .replace(#id, format!("{}", #pv).as_str())
224 });
225 }
226 stream
227 };
228
229 let query = if querys.is_empty() {
230 quote! {}
231 } else {
232 quote! {
233 .query(&[#(#querys),*])
234 }
235 };
236
237 let req_body = match body {
238 None => quote! {},
239 Some(Form(form)) => quote! {
240 .form(#form)
241 },
242 Some(Json(json)) => quote! {
243 .json(#json)
244 },
245 };
246
247 let request_builder_body = match body {
248 None => quote! {
249 feign::RequestBody::None
250 },
251 Some(Form(form)) => quote! {
252 feign::RequestBody::Form(::feign::re_exports::serde_json::to_value(#form)?)
253 },
254 Some(Json(json)) => quote! {
255 feign::RequestBody::Json(::feign::re_exports::serde_json::to_value(#json)?)
256 },
257 };
258
259 let header_mut: proc_macro2::TokenStream = match headers {
260 None => quote! {},
261 Some(_) => quote! {mut},
262 };
263
264 let headers_point = match headers {
265 None => quote! {None},
266 Some(headers) => quote! {Some(#headers)},
267 };
268
269 let headers = match headers {
270 None => quote! {},
271 Some(headers) => quote! {
272 for header in #headers.clone() {
273 req = req.header(header.0,header.1);
274 }
275 },
276 };
277
278 let params = quote! {
279 #query
280 #req_body
281 };
282
283 let inputs = inputs
284 .iter()
285 .filter_map(|fn_arg| match fn_arg {
286 FnArg::Receiver(_) => None,
287 FnArg::Typed(a) => {
288 let mut a = a.clone();
289 a.attrs.clear();
290 Some(FnArg::Typed(a))
291 }
292 })
293 .collect::<syn::punctuated::Punctuated<_, syn::Token![,]>>();
294
295 let before_send_builder = match before_send {
296 Some(builder) => {
297 let builder_token: proc_macro2::TokenStream = builder.clone().parse().unwrap();
298 quote! {
299 #builder_token(
300 req,
301 #_http_method_token,
302 self.host.host().to_string(),
303 self.path.clone(),
304 request_path.clone(),
305 #request_builder_body,
306 #headers_point,
307 ).await?
308 }
309 }
310 None => quote! {
311 req
312 },
313 };
314
315 let deserialize = match request.deserialize {
316 None => quote! {::feign::re_exports::serde_json::from_str(text.as_str())},
317 Some(deserialize) => {
318 let builder_token: proc_macro2::TokenStream = deserialize.parse().unwrap();
319 quote! {#builder_token(text).await}
320 }
321 };
322
323 quote! {
324 pub async fn #name(&self, #inputs) #output {
325 let request_path = String::from(#req_path)#path_variables;
326 let url = format!("{}{}{}", self.host, self.path, request_path);
327 let #header_mut req = #reqwest_client_builder
328 .#http_method_ident(url.as_str())
329 #params;
330 #headers;
331 let req = #before_send_builder;
332 let text = req
333 .send()
334 .await?
335 .error_for_status()?
336 .text()
337 .await?;
338 Ok(#deserialize?)
339 }
340 }
341}
342
343enum HttpMethod {
345 Get,
346 Post,
347 Put,
348 Patch,
349 Delete,
350 Head,
351}
352
353fn http_method_from_ident(ident: &syn::Ident) -> Option<HttpMethod> {
354 Some(match &*ident.to_string() {
355 "get" => HttpMethod::Get,
356 "post" => HttpMethod::Post,
357 "put" => HttpMethod::Put,
358 "patch" => HttpMethod::Patch,
359 "delete" => HttpMethod::Delete,
360 "head" => HttpMethod::Head,
361 _ => return None,
362 })
363}
364
365fn http_method_to_token(method: HttpMethod) -> proc_macro2::TokenStream {
366 match method {
367 HttpMethod::Get => "feign::HttpMethod::Get",
368 HttpMethod::Post => "feign::HttpMethod::Post",
369 HttpMethod::Put => "feign::HttpMethod::Put",
370 HttpMethod::Patch => "feign::HttpMethod::Patch",
371 HttpMethod::Delete => "feign::HttpMethod::Delete",
372 HttpMethod::Head => "feign::HttpMethod::Head",
373 }
374 .parse()
375 .unwrap()
376}
377
378enum RequestBody<'a> {
380 Form(&'a Box<syn::Pat>),
381 Json(&'a Box<syn::Pat>),
382}
383
384#[derive(Debug, FromMeta)]
386struct ClientArgs {
387 #[darling(default)]
388 pub host: Option<String>,
389 pub path: String,
390 #[darling(default)]
391 pub client_builder: Option<String>,
392 #[darling(default)]
393 pub before_send: Option<String>,
394}
395
396#[derive(Debug, FromMeta)]
398struct Request {
399 pub path: String,
400 #[darling(default)]
401 pub deserialize: Option<String>,
402}