#[macro_use]
extern crate syn;
use proc_macro::TokenStream;
use quote::quote;
use syn::punctuated::Punctuated;
use syn::Expr::MethodCall;
use syn::{ExprArray, ExprMethodCall, Item, Item::Fn, ItemFn, ItemMod, Path};
#[proc_macro]
pub fn module(input: TokenStream) -> TokenStream {
let path = parse_macro_input!(input as Path);
TokenStream::from(quote!(#path::__routes()))
}
#[proc_macro_attribute]
pub fn route_module(_metadata: TokenStream, input: TokenStream) -> TokenStream {
let module = parse_macro_input!(input as ItemMod);
let content = module.content
.expect("Currently, only modules which declare their content in-place ('mod my_module { ... }') are supported!");
let routes = content
.1
.iter()
.filter_map(|item| {
if let Fn(func) = item {
Some(func)
} else {
None
}
})
.filter(|&func| is_rocket_route(func))
.map(|route| route.sig.ident.clone())
.collect::<Vec<_>>();
let routes_len = routes.len();
let mut elems = Punctuated::new();
for route in routes {
let route_expr = TokenStream::from(quote!(#route { }.into_route()));
let call = MethodCall(parse_macro_input!(route_expr as ExprMethodCall));
elems.push(call);
}
let route_literal = ExprArray {
attrs: vec![],
bracket_token: Default::default(),
elems,
};
let fn_routes = TokenStream::from(quote! {
#[doc(hidden)]
pub fn __routes() -> [rocket::Route; #routes_len] {
#route_literal
}
});
let mut items = content.1.clone();
items.push(Item::from(parse_macro_input!(fn_routes as ItemFn)));
let module = ItemMod {
content: Some((content.0, items)),
..module
};
TokenStream::from(quote!(#module))
}
fn is_rocket_route(func: &ItemFn) -> bool {
func.attrs.iter().any(|attr| {
attr.path.segments.iter().any(|s| {
ROCKET_ROUTE_KEYWORDS
.iter()
.any(|&keyword| s.ident == keyword)
})
})
}
const ROCKET_ROUTE_KEYWORDS: [&str; 8] = [
"route", "get", "post", "put", "delete", "head", "patch", "options",
];