reset_router_macros/
lib.rs

1extern crate proc_macro;
2extern crate proc_macro2;
3extern crate proc_macro_hack;
4extern crate quote;
5extern crate syn;
6
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9use proc_macro_hack::proc_macro_hack;
10use quote::quote;
11
12use syn::*;
13
14#[proc_macro_hack]
15pub fn routes(item: TokenStream) -> TokenStream {
16    use syn::parse::Parser;
17
18    let paths = <punctuated::Punctuated<Path, token::Comma>>::parse_terminated.parse(item).unwrap();
19
20    let parts = paths
21        .iter()
22        .map(|path| {
23            let mut fn_name_path = path.clone();
24            {
25                let mut last_seg = fn_name_path.segments.last_mut().unwrap();
26                last_seg.value_mut().ident = Ident::new(
27                    &format!(
28                        "RESET_ROUTER_ROUTE_PARTS_FOR_{}",
29                        last_seg.value().ident.to_string().trim_start_matches("r#").to_owned()
30                    ),
31                    Span::call_site(),
32                );
33            }
34            let fn_name_path = &fn_name_path;
35            quote!({
36                let (method, regex, priority) = #fn_name_path();
37                (method, regex, priority, #path.into())
38            })
39        })
40        .collect::<Vec<_>>();
41
42    let out = quote!(vec![#(#parts),*]);
43
44    out.into()
45}
46
47/// Use like `#[options("/path")]`
48///
49/// Optionally include priority, e.g.: `#[options("/path", priority=1)]`
50#[proc_macro_attribute]
51pub fn options(attrs: TokenStream, item: TokenStream) -> TokenStream {
52    named_route(attrs, item, "OPTIONS")
53}
54
55/// Use like `#[get("/path")]`
56///
57/// Optionally include priority, e.g.: `#[get("/path", priority=1)]`
58#[proc_macro_attribute]
59pub fn get(attrs: TokenStream, item: TokenStream) -> TokenStream {
60    named_route(attrs, item, "GET")
61}
62
63/// Use like `#[post("/path")]`
64///
65/// Optionally include priority, e.g.: `#[post("/path", priority=1)]`
66#[proc_macro_attribute]
67pub fn post(attrs: TokenStream, item: TokenStream) -> TokenStream {
68    named_route(attrs, item, "POST")
69}
70
71/// Use like `#[put("/path")]`
72///
73/// Optionally include priority, e.g.: `#[put("/path", priority=1)]`
74#[proc_macro_attribute]
75pub fn put(attrs: TokenStream, item: TokenStream) -> TokenStream {
76    named_route(attrs, item, "PUT")
77}
78
79/// Use like `#[delete("/path")]`
80///
81/// Optionally include priority, e.g.: `#[delete("/path", priority=1)]`
82#[proc_macro_attribute]
83pub fn delete(attrs: TokenStream, item: TokenStream) -> TokenStream {
84    named_route(attrs, item, "DELETE")
85}
86
87/// Use like `#[head("/path")]`
88///
89/// Optionally include priority, e.g.: `#[head("/path", priority=1)]`
90#[proc_macro_attribute]
91pub fn head(attrs: TokenStream, item: TokenStream) -> TokenStream {
92    named_route(attrs, item, "HEAD")
93}
94
95/// Use like `#[trace("/path")]`
96///
97/// Optionally include priority, e.g.: `#[trace("/path", priority=1)]`
98#[proc_macro_attribute]
99pub fn trace(attrs: TokenStream, item: TokenStream) -> TokenStream {
100    named_route(attrs, item, "TRACE")
101}
102
103/// Use like `#[connect("/path")]`
104///
105/// Optionally include priority, e.g.: `#[connect("/path", priority=1)]`
106#[proc_macro_attribute]
107pub fn connect(attrs: TokenStream, item: TokenStream) -> TokenStream {
108    named_route(attrs, item, "CONNECT")
109}
110
111/// Use like `#[patch("/path")]`
112///
113/// Optionally include priority, e.g.: `#[patch("/path", priority=1)]`
114#[proc_macro_attribute]
115pub fn patch(attrs: TokenStream, item: TokenStream) -> TokenStream {
116    named_route(attrs, item, "PATCH")
117}
118
119/// Use like `#[route(path="/path")]`
120///
121/// Optionally include comma separated HTTP methods to match , e.g.: `#[route(path="/path", methods="GET, POST")]`
122///
123/// Optionally include priority, e.g.: `#[route(path="/path", methods="GET, POST", priority=1)]`
124#[proc_macro_attribute]
125pub fn route(attrs: TokenStream, item: TokenStream) -> TokenStream {
126    let attrs = parse_macro_input!(attrs as AttributeArgs);
127    route_inner(attrs, item)
128}
129
130fn named_route(attrs: TokenStream, item: TokenStream, method: &str) -> TokenStream {
131    let attrs = parse_macro_input!(attrs as AttributeArgs);
132    let mut new_attrs = Vec::with_capacity(attrs.len());
133    for attr in attrs.into_iter() {
134        if let NestedMeta::Literal(ref lit) = attr {
135            new_attrs.push(NestedMeta::Meta(Meta::NameValue(parse_quote!(path=#lit))));
136        } else {
137            new_attrs.push(attr);
138        }
139    }
140    new_attrs.push(NestedMeta::Meta(Meta::NameValue(parse_quote!(methods=#method))));
141    route_inner(new_attrs, item)
142}
143
144fn route_inner(attrs: AttributeArgs, item: TokenStream) -> TokenStream {
145    let item = parse_macro_input!(item as ItemFn);
146
147    let params = attrs
148        .iter()
149        .filter_map(|x| match x {
150            NestedMeta::Meta(y) => Some(y),
151            _ => None,
152        })
153        .filter_map(|x| match x {
154            Meta::NameValue(y) => Some(y),
155            _ => None,
156        });
157
158    let regex_str = params
159        .clone()
160        .find(|x| x.ident == "path")
161        .and_then(|x| match x.lit {
162            Lit::Str(ref y) => Some(y),
163            _ => None,
164        })
165        .map(|x| x.value())
166        .expect("No path provided");
167
168    let methods_str = params
169        .clone()
170        .find(|x| x.ident == "methods")
171        .and_then(|x| match x.lit {
172            Lit::Str(ref y) => Some(y),
173            _ => None,
174        })
175        .map(|x| x.value())
176        .unwrap_or_else(|| "".into());
177
178    let priority_int = params
179        .clone()
180        .find(|x| x.ident == "priority")
181        .and_then(|x| match x.lit {
182            Lit::Int(ref y) => Some(y),
183            _ => None,
184        })
185        .map(|x| x.value() as u8)
186        .unwrap_or_else(|| 0);
187
188    let method_bits = {
189        let mut method_iter = methods_str.split(',').map(|x| x.trim().to_lowercase());
190
191        let cls = |s: &str| match s {
192            "options" => quote!(reset_router::bits::Method::OPTIONS),
193            "get" => quote!(reset_router::bits::Method::GET),
194            "post" => quote!(reset_router::bits::Method::POST),
195            "put" => quote!(reset_router::bits::Method::PUT),
196            "delete" => quote!(reset_router::bits::Method::DELETE),
197            "head" => quote!(reset_router::bits::Method::HEAD),
198            "trace" => quote!(reset_router::bits::Method::TRACE),
199            "connect" => quote!(reset_router::bits::Method::CONNECT),
200            "patch" => quote!(reset_router::bits::Method::PATCH),
201            _ => panic!("Unknown method parameter"),
202        };
203
204        let first = method_iter.next();
205
206        match first {
207            Some(m) => {
208                let base = cls(&m);
209                method_iter.fold(base, |acc, val| {
210                    let val = cls(&val);
211                    quote!(#acc | #val)
212                })
213            }
214            _ => quote!(reset_router::bits::Method::all()),
215        }
216    };
217
218    let fn_name = Ident::new(
219        &format!(
220            "RESET_ROUTER_ROUTE_PARTS_FOR_{}",
221            item.ident.to_string().trim_start_matches("r#").to_owned()
222        ),
223        Span::call_site(),
224    );
225
226    let out = quote! {
227        #item
228        pub fn #fn_name() -> (u32, &'static str, u8) {
229            ((#method_bits).bits(), #regex_str, #priority_int)
230        }
231    };
232
233    out.into()
234}