1#[macro_use]
8extern crate synstructure;
9extern crate proc_macro;
10
11mod error;
12mod params;
13mod parse;
14
15use std::{collections::HashMap, convert::TryFrom};
16
17use error::Error;
18use params::Parameters;
19use proc_macro2::Span;
20use quote::quote;
21use regex::Regex;
22use syn::{self, spanned::Spanned, Field, Generics, Ident, Meta};
23
24const MACRO_NAME: &str = "Endpoint";
25const ATTR_NAME: &str = "endpoint";
26
27#[derive(Debug, PartialEq, Eq, Hash)]
28pub(crate) enum EndpointAttribute {
29 Body,
30 Query,
31 Raw,
32 Skip,
33 Untagged,
34}
35
36impl TryFrom<&Meta> for EndpointAttribute {
37 type Error = Error;
38 fn try_from(m: &Meta) -> Result<Self, Self::Error> {
39 match m.path().get_ident() {
40 Some(i) => match i.to_string().to_lowercase().as_str() {
41 "body" => Ok(EndpointAttribute::Body),
42 "query" => Ok(EndpointAttribute::Query),
43 "raw" => Ok(EndpointAttribute::Raw),
44 "skip" => Ok(EndpointAttribute::Skip),
45 _ => Err(Error::new(
46 m.span(),
47 format!("Unknown attribute: {}", i).as_str(),
48 )),
49 },
50 None => Err(Error::new(m.span(), "Invalid attribute")),
51 }
52 }
53}
54
55fn gen_path(path: &syn::LitStr) -> Result<proc_macro2::TokenStream, Error> {
73 let re = Regex::new(r"\{(.*?)\}").unwrap();
74 let mut fmt_args: Vec<syn::Expr> = Vec::new();
75 for cap in re.captures_iter(path.value().as_str()) {
76 let expr = syn::parse_str(&cap[1]);
77 match expr {
78 Ok(ex) => fmt_args.push(ex),
79 Err(_) => {
80 return Err(Error::new(
81 path.span(),
82 format!("Failed parsing format argument as expression: {}", &cap[1]).as_str(),
83 ));
84 }
85 }
86 }
87 let path = syn::LitStr::new(
88 re.replace_all(path.value().as_str(), "{}")
89 .to_string()
90 .as_str(),
91 Span::call_site(),
92 );
93
94 if !fmt_args.is_empty() {
95 Ok(quote! {
96 format!(#path, #(#fmt_args),*)
97 })
98 } else {
99 Ok(quote! {
100 String::from(#path)
101 })
102 }
103}
104
105fn gen_query(
112 fields: &HashMap<EndpointAttribute, Vec<Field>>,
113 serde_attrs: &[Meta],
114) -> proc_macro2::TokenStream {
115 let query_fields = fields.get(&EndpointAttribute::Query);
116 if let Some(v) = query_fields {
117 let temp = parse::fields_to_struct(v, serde_attrs);
119 quote! {
120 fn query(&self) -> Result<Option<String>, ClientError> {
121 #temp
122
123 Ok(Some(build_query(&__temp)?))
124 }
125 }
126 } else {
127 quote! {}
128 }
129}
130
131fn gen_body(
148 fields: &HashMap<EndpointAttribute, Vec<Field>>,
149 serde_attrs: &[Meta],
150) -> Result<proc_macro2::TokenStream, Error> {
151 if let Some(v) = fields.get(&EndpointAttribute::Raw) {
153 if v.len() > 1 {
154 return Err(Error::new(v[1].span(), "May only mark one field as raw"));
155 }
156
157 let id = v[0].ident.clone().unwrap();
158 Ok(quote! {
159 fn body(&self) -> Result<Option<Vec<u8>>, ClientError>{
160 Ok(Some(self.#id.clone()))
161 }
162 })
163 } else if let Some(v) = fields.get(&EndpointAttribute::Body) {
165 let temp = parse::fields_to_struct(v, serde_attrs);
166 Ok(quote! {
167 fn body(&self) -> Result<Option<Vec<u8>>, ClientError> {
168 #temp
169
170 Ok(Some(build_body(&__temp, Self::REQUEST_BODY_TYPE)?))
171 }
172 })
173 } else if let Some(v) = fields.get(&EndpointAttribute::Untagged) {
175 let temp = parse::fields_to_struct(v, serde_attrs);
176 Ok(quote! {
177 fn body(&self) -> Result<Option<Vec<u8>>, ClientError> {
178 #temp
179
180 Ok(Some(build_body(&__temp, Self::REQUEST_BODY_TYPE)?))
181 }
182 })
183 } else {
185 Ok(quote! {})
186 }
187}
188
189fn gen_builder(id: &Ident, generics: &Generics) -> proc_macro2::TokenStream {
196 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
197 let builder_id: syn::Type = syn::parse_str(format!("{}Builder", id).as_str()).unwrap();
198 let builder_func: syn::Expr =
199 syn::parse_str(format!("{}Builder::default()", id).as_str()).unwrap();
200
201 quote! {
202 impl #impl_generics #id #ty_generics #where_clause {
203 pub fn builder() -> #builder_id #ty_generics {
204 #builder_func
205 }
206 }
207 }
208}
209
210fn parse_params(attr: &Meta) -> Result<Parameters, Error> {
213 let kv = parse::attr_kv(attr)?;
215
216 let map = parse::to_map(&kv)?;
218
219 params::Parameters::new(map)
221}
222
223fn endpoint_derive(s: synstructure::Structure) -> proc_macro2::TokenStream {
225 let attrs = match parse::attributes(&s.ast().attrs, ATTR_NAME) {
227 Ok(v) => v,
228 Err(e) => return e.into_tokens(),
229 };
230
231 let field_attrs = match parse::field_attributes(&s.ast().data) {
233 Ok(v) => v,
234 Err(e) => return e.into_tokens(),
235 };
236
237 if attrs.is_empty() {
239 return Error::new(
240 Span::call_site(),
241 format!(
242 "Deriving `{}` requires attaching an `{}` attribute",
243 MACRO_NAME, ATTR_NAME
244 )
245 .as_str(),
246 )
247 .into_tokens();
248 }
249
250 if attrs.len() > 1 {
252 return Error::new(
253 Span::call_site(),
254 format!("Cannot define the {} attribute more than once", ATTR_NAME).as_str(),
255 )
256 .into_tokens();
257 }
258
259 let params = match parse_params(&attrs[0]) {
261 Ok(v) => v,
262 Err(e) => return e.into_tokens(),
263 };
264
265 let path = params.path;
266 let method = params.method;
267 let response = params.response;
268 let request_type = params.request_type;
269 let response_type = params.response_type;
270 let id = &s.ast().ident;
271
272 let serde_attrs = parse::attributes(&s.ast().attrs, "serde");
274 let serde_attrs = serde_attrs.unwrap_or_default();
275
276 let path = match gen_path(&path) {
278 Ok(a) => a,
279 Err(e) => return e.into_tokens(),
280 };
281
282 let query = gen_query(&field_attrs, &serde_attrs);
284
285 let body = match gen_body(&field_attrs, &serde_attrs) {
287 Ok(d) => d,
288 Err(e) => return e.into_tokens(),
289 };
290
291 let builder = match params.builder {
293 true => gen_builder(&s.ast().ident, &s.ast().generics),
294 false => quote! {},
295 };
296
297 let (impl_generics, ty_generics, where_clause) = s.ast().generics.split_for_impl();
299
300 let const_name = format!("_DERIVE_Endpoint_FOR_{}", id);
302 let const_ident = Ident::new(const_name.as_str(), Span::call_site());
303 quote! {
304 #[allow(non_local_definitions)]
305 const #const_ident: () = {
306 use rustify::__private::serde::Serialize;
307 use rustify::http::{build_body, build_query};
308 use rustify::client::Client;
309 use rustify::endpoint::Endpoint;
310 use rustify::enums::{RequestMethod, RequestType, ResponseType};
311 use rustify::errors::ClientError;
312
313 impl #impl_generics Endpoint for #id #ty_generics #where_clause {
314 type Response = #response;
315 const REQUEST_BODY_TYPE: RequestType = RequestType::#request_type;
316 const RESPONSE_BODY_TYPE: ResponseType = ResponseType::#response_type;
317
318 fn path(&self) -> String {
319 #path
320 }
321
322 fn method(&self) -> RequestMethod {
323 RequestMethod::#method
324 }
325
326 #query
327
328
329 #body
330 }
331
332 #builder
333 };
334 }
335}
336
337synstructure::decl_derive!([Endpoint, attributes(endpoint)] => endpoint_derive);