2019-03-31 09:23:15 +02:00
|
|
|
extern crate proc_macro;
|
|
|
|
|
|
|
|
use proc_macro::TokenStream;
|
2019-10-14 17:34:17 +02:00
|
|
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
|
|
|
use quote::{quote, ToTokens, TokenStreamExt};
|
|
|
|
use syn::{AttributeArgs, Ident, NestedMeta};
|
2019-03-31 09:23:15 +02:00
|
|
|
|
|
|
|
enum ResourceType {
|
|
|
|
Async,
|
|
|
|
Sync,
|
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
impl ToTokens for ResourceType {
|
|
|
|
fn to_tokens(&self, stream: &mut TokenStream2) {
|
|
|
|
let ident = match self {
|
2019-11-21 16:34:04 +01:00
|
|
|
ResourceType::Async => "to",
|
2019-10-14 17:34:17 +02:00
|
|
|
ResourceType::Sync => "to",
|
|
|
|
};
|
|
|
|
let ident = Ident::new(ident, Span::call_site());
|
|
|
|
stream.append(ident);
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq)]
|
|
|
|
pub enum GuardType {
|
|
|
|
Get,
|
|
|
|
Post,
|
|
|
|
Put,
|
|
|
|
Delete,
|
2019-06-04 18:30:43 +02:00
|
|
|
Head,
|
|
|
|
Connect,
|
|
|
|
Options,
|
|
|
|
Trace,
|
2019-06-05 04:43:13 +02:00
|
|
|
Patch,
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
impl GuardType {
|
|
|
|
fn as_str(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
GuardType::Get => "Get",
|
|
|
|
GuardType::Post => "Post",
|
|
|
|
GuardType::Put => "Put",
|
|
|
|
GuardType::Delete => "Delete",
|
|
|
|
GuardType::Head => "Head",
|
|
|
|
GuardType::Connect => "Connect",
|
|
|
|
GuardType::Options => "Options",
|
|
|
|
GuardType::Trace => "Trace",
|
|
|
|
GuardType::Patch => "Patch",
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
impl ToTokens for GuardType {
|
|
|
|
fn to_tokens(&self, stream: &mut TokenStream2) {
|
|
|
|
let ident = self.as_str();
|
|
|
|
let ident = Ident::new(ident, Span::call_site());
|
|
|
|
stream.append(ident);
|
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
struct Args {
|
|
|
|
path: syn::LitStr,
|
|
|
|
guards: Vec<Ident>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Args {
|
|
|
|
fn new(args: AttributeArgs) -> syn::Result<Self> {
|
|
|
|
let mut path = None;
|
|
|
|
let mut guards = Vec::new();
|
|
|
|
for arg in args {
|
|
|
|
match arg {
|
|
|
|
NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
|
|
|
|
None => {
|
|
|
|
path = Some(lit);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
lit,
|
|
|
|
"Multiple paths specified! Should be only one!",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
|
|
|
|
if nv.path.is_ident("guard") {
|
|
|
|
if let syn::Lit::Str(lit) = nv.lit {
|
|
|
|
guards.push(Ident::new(&lit.value(), Span::call_site()));
|
|
|
|
} else {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
nv.lit,
|
|
|
|
"Attribute guard expects literal string!",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
nv.path,
|
|
|
|
"Unknown attribute key is specified. Allowed: guard",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
arg => {
|
|
|
|
return Err(syn::Error::new_spanned(arg, "Unknown attribute"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(Args {
|
|
|
|
path: path.unwrap(),
|
|
|
|
guards,
|
|
|
|
})
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
pub struct Route {
|
|
|
|
name: syn::Ident,
|
|
|
|
args: Args,
|
|
|
|
ast: syn::ItemFn,
|
|
|
|
resource_type: ResourceType,
|
|
|
|
guard: GuardType,
|
|
|
|
}
|
|
|
|
|
2019-03-31 09:23:15 +02:00
|
|
|
fn guess_resource_type(typ: &syn::Type) -> ResourceType {
|
|
|
|
let mut guess = ResourceType::Sync;
|
|
|
|
|
2019-07-17 11:08:30 +02:00
|
|
|
if let syn::Type::ImplTrait(typ) = typ {
|
|
|
|
for bound in typ.bounds.iter() {
|
|
|
|
if let syn::TypeParamBound::Trait(bound) = bound {
|
|
|
|
for bound in bound.path.segments.iter() {
|
|
|
|
if bound.ident == "Future" {
|
|
|
|
guess = ResourceType::Async;
|
|
|
|
break;
|
|
|
|
} else if bound.ident == "Responder" {
|
|
|
|
guess = ResourceType::Sync;
|
|
|
|
break;
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
2019-04-10 21:24:17 +02:00
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
2019-04-10 21:24:17 +02:00
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
guess
|
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
impl Route {
|
|
|
|
pub fn new(
|
|
|
|
args: AttributeArgs,
|
|
|
|
input: TokenStream,
|
|
|
|
guard: GuardType,
|
|
|
|
) -> syn::Result<Self> {
|
2019-03-31 09:23:15 +02:00
|
|
|
if args.is_empty() {
|
2019-10-14 17:34:17 +02:00
|
|
|
return Err(syn::Error::new(
|
|
|
|
Span::call_site(),
|
|
|
|
format!(
|
|
|
|
r#"invalid server definition, expected #[{}("<some path>")]"#,
|
|
|
|
guard.as_str().to_ascii_lowercase()
|
|
|
|
),
|
|
|
|
));
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
2019-10-14 17:34:17 +02:00
|
|
|
let ast: syn::ItemFn = syn::parse(input)?;
|
|
|
|
let name = ast.sig.ident.clone();
|
2019-03-31 09:23:15 +02:00
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
let args = Args::new(args)?;
|
2019-03-31 09:23:15 +02:00
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
let resource_type = if ast.sig.asyncness.is_some() {
|
2019-03-31 09:23:15 +02:00
|
|
|
ResourceType::Async
|
|
|
|
} else {
|
2019-10-14 17:34:17 +02:00
|
|
|
match ast.sig.output {
|
|
|
|
syn::ReturnType::Default => {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
ast,
|
|
|
|
"Function has no return type. Cannot be used as handler",
|
|
|
|
));
|
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
Ok(Self {
|
2019-03-31 09:23:15 +02:00
|
|
|
name,
|
2019-10-14 17:34:17 +02:00
|
|
|
args,
|
2019-03-31 09:23:15 +02:00
|
|
|
ast,
|
|
|
|
resource_type,
|
|
|
|
guard,
|
2019-10-14 17:34:17 +02:00
|
|
|
})
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn generate(&self) -> TokenStream {
|
2019-10-14 17:34:17 +02:00
|
|
|
let name = &self.name;
|
|
|
|
let guard = &self.guard;
|
|
|
|
let ast = &self.ast;
|
|
|
|
let path = &self.args.path;
|
|
|
|
let extra_guards = &self.args.guards;
|
|
|
|
let resource_type = &self.resource_type;
|
|
|
|
let stream = quote! {
|
|
|
|
#[allow(non_camel_case_types)]
|
|
|
|
pub struct #name;
|
|
|
|
|
|
|
|
impl actix_web::dev::HttpServiceFactory for #name {
|
|
|
|
fn register(self, config: &mut actix_web::dev::AppService) {
|
|
|
|
#ast
|
|
|
|
|
|
|
|
let resource = actix_web::Resource::new(#path)
|
|
|
|
.guard(actix_web::guard::#guard())
|
|
|
|
#(.guard(actix_web::guard::fn_guard(#extra_guards)))*
|
|
|
|
.#resource_type(#name);
|
|
|
|
|
|
|
|
actix_web::dev::HttpServiceFactory::register(resource, config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
stream.into()
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
}
|