rest_client_codegen/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3
4use crate::proc_macro::TokenStream;
5use quote::quote;
6use syn;
7
8#[proc_macro_attribute]
9pub fn rest(attr: TokenStream, item: TokenStream) -> TokenStream {
10    let args = syn::parse_macro_input!(attr as syn::AttributeArgs);
11    if args.is_empty() {
12        panic!("invalid number of arguments");
13    }
14
15    // https://github.com/actix/actix-web/blob/1970c99522ef37d4a5fbed404b9b100912fad69a/actix-web-codegen/src/lib.rs#L18
16    let path = match args[0] {
17        syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
18            let fname = quote!(#fname).to_string();
19            fname.as_str()[1..fname.len() - 1].to_owned()
20        }
21        _ => panic!("resource path"),
22    };
23
24    let mut is_vec = false;
25    let mut wrapper: Option<syn::Ident> = None;
26    for arg in args[1..].iter() {
27        match arg {
28            syn::NestedMeta::Meta(syn::Meta::Word(ref fname))
29                if fname == &syn::Ident::new("vec", proc_macro2::Span::call_site()) =>
30            {
31                is_vec = true;
32            }
33            syn::NestedMeta::Meta(syn::Meta::NameValue(ref value))
34                if value.ident == syn::Ident::new("wrapper", proc_macro2::Span::call_site()) =>
35            {
36                let lit = if let syn::Lit::Str(name) = &value.lit {
37                    name
38                } else {
39                    panic!("wrapper has not the format wrapper = \"Type\"");
40                };
41                wrapper = Some(syn::Ident::new(&lit.value(), lit.span()));
42            }
43            _ => {}
44        }
45    }
46
47    let item_copy = item.clone();
48    let input = syn::parse_macro_input!(item_copy as syn::ItemStruct);
49    let item: proc_macro2::TokenStream = item.into();
50    let ident = &input.ident;
51
52    let mut result_type = if is_vec {
53        quote! { Vec<Self> }
54    } else {
55        quote! { Self }
56    };
57
58    if let Some(wrapper) = wrapper {
59        result_type = quote! { #wrapper<#result_type> };
60    };
61
62    let final_trait = quote! { ClientMethods<#result_type> };
63
64    let count = path.matches("{}").count();
65    let mut counter_vec = Vec::new();
66    for _i in 0..count {
67        counter_vec.push(quote! { iter.next().unwrap() });
68    }
69
70    let result = quote! {
71        impl #final_trait for #ident {
72            fn get(parameters: impl IntoIterator<Item = impl std::fmt::Display>) -> Result<#result_type, Box<std::error::Error>> {
73                let mut iter = parameters.into_iter();
74                let request_path = format!(#path, #(#counter_vec),*);
75                let new_self: #result_type = reqwest::get(&request_path)?
76                    .json()?;
77                Ok(new_self)
78            }
79        }
80    };
81
82    (quote! {
83        #item
84        #result
85    })
86    .into()
87}