extern crate proc_macro;
extern crate rocket;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, AttributeArgs, ItemFn, Meta, NestedMeta};
use autortr_rocket_core::{DATA, EMPTY, METHOD, NAMESPACE, PATH, ROOT};
#[proc_macro_attribute]
pub fn request_mapping(args: TokenStream, item: TokenStream) -> TokenStream {
let parsed_args = parse_macro_input!(args as AttributeArgs);
let function = parse_macro_input!(item as ItemFn);
let function_ident = &function.sig.ident;
let function_name = function_ident.to_string();
let (namespace, method, path, data) = match parse_request_mapping_args(parsed_args) {
Ok((n, m, p, d)) => (n, m, p, d),
Err(_) => panic!("Invalid arguments to `#[request_mapping]`"),
};
let namespace = namespace.unwrap_or_else(|| ROOT.to_string());
let data = data.unwrap_or_else(|| EMPTY.to_string());
let route = match method.as_str() {
"get" => match data.as_str() {
"_" => quote! { #[rocket::get(#path)] },
_ => quote! { #[rocket::get(#path, data = #data)] },
},
"post" => match data.as_str() {
"_" => quote! { #[rocket::post(#path)] },
_ => quote! { #[rocket::post(#path, data = #data)] },
},
"put" => match data.as_str() {
"_" => quote! { #[rocket::put(#path)] },
_ => quote! { #[rocket::put(#path, data = #data)] },
},
"patch" => match data.as_str() {
"_" => quote! { #[rocket::patch(#path)] },
_ => quote! { #[rocket::patch(#path, data = #data)] },
},
"delete" => match data.as_str() {
"_" => quote! { #[rocket::delete(#path)] },
_ => quote! { #[rocket::delete(#path, data = #data)] },
},
"head" => match data.as_str() {
"_" => quote! { #[rocket::head(#path)] },
_ => quote! { #[rocket::head(#path, data = #data)] },
},
_ => panic!("Unsupported HTTP method"),
};
let register_fn_name = format_ident!("_register_{}_", function_ident);
let register_fn = quote! {
#[ctor::ctor]
fn #register_fn_name() {
register_route_mapping(RouteMapping {
function: #function_name.to_string(),
namespace: #namespace.to_string(),
method: #method.to_string(),
path: #path.to_string(),
data: #data.to_string(),
routes: rocket::routes![#function_ident],
});
}
};
let expanded = quote! {
#route
#function
#register_fn
};
expanded.into()
}
fn parse_request_mapping_args(
args: AttributeArgs,
) -> Result<(Option<String>, String, String, Option<String>), ()> {
let mut namespace = None;
let mut method = None;
let mut path = None;
let mut data = None;
for arg in args {
match arg {
NestedMeta::Meta(Meta::NameValue(nv)) => {
if nv.path.is_ident(NAMESPACE) {
if let syn::Lit::Str(n) = nv.lit {
namespace = Some(n.value());
}
} else if nv.path.is_ident(METHOD) {
if let syn::Lit::Str(m) = nv.lit {
method = Some(m.value());
}
} else if nv.path.is_ident(PATH) {
if let syn::Lit::Str(p) = nv.lit {
path = Some(p.value());
}
} else if nv.path.is_ident(DATA) {
if let syn::Lit::Str(d) = nv.lit {
data = Some(d.value());
}
}
}
_ => return Err(()),
}
}
match (method, path) {
(Some(m), Some(p)) => Ok((namespace, m, p, data)),
_ => Err(()),
}
}