unify #route and #get, #post ...

This commit is contained in:
Arniu 2020-09-22 04:18:21 +08:00
parent f7bcad9567
commit 561d7b8bf0
2 changed files with 62 additions and 64 deletions

View File

@ -58,7 +58,7 @@ use proc_macro::TokenStream;
/// - `wrap = "Middleware"` - Registers a resource middleware. /// - `wrap = "Middleware"` - Registers a resource middleware.
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Get) route::with_method(Some(route::Method::Get), args, input)
} }
/// Creates route handler with `POST` method guard. /// Creates route handler with `POST` method guard.
@ -68,7 +68,7 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html) /// Attributes are the same as in [get](attr.get.html)
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Post) route::with_method(Some(route::Method::Post), args, input)
} }
/// Creates route handler with `PUT` method guard. /// Creates route handler with `PUT` method guard.
@ -78,7 +78,7 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html) /// Attributes are the same as in [get](attr.get.html)
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Put) route::with_method(Some(route::Method::Put), args, input)
} }
/// Creates route handler with `DELETE` method guard. /// Creates route handler with `DELETE` method guard.
@ -88,7 +88,7 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html). /// Attributes are the same as in [get](attr.get.html).
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Delete) route::with_method(Some(route::Method::Delete), args, input)
} }
/// Creates route handler with `HEAD` method guard. /// Creates route handler with `HEAD` method guard.
@ -98,7 +98,7 @@ pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html). /// Attributes are the same as in [get](attr.get.html).
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Head) route::with_method(Some(route::Method::Head), args, input)
} }
/// Creates route handler with `CONNECT` method guard. /// Creates route handler with `CONNECT` method guard.
@ -108,7 +108,7 @@ pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html). /// Attributes are the same as in [get](attr.get.html).
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Connect) route::with_method(Some(route::Method::Connect), args, input)
} }
/// Creates route handler with `OPTIONS` method guard. /// Creates route handler with `OPTIONS` method guard.
@ -118,7 +118,7 @@ pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html). /// Attributes are the same as in [get](attr.get.html).
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Options) route::with_method(Some(route::Method::Options), args, input)
} }
/// Creates route handler with `TRACE` method guard. /// Creates route handler with `TRACE` method guard.
@ -128,7 +128,7 @@ pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html). /// Attributes are the same as in [get](attr.get.html).
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Trace) route::with_method(Some(route::Method::Trace), args, input)
} }
/// Creates route handler with `PATCH` method guard. /// Creates route handler with `PATCH` method guard.
@ -138,7 +138,7 @@ pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
/// Attributes are the same as in [get](attr.get.html). /// Attributes are the same as in [get](attr.get.html).
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Patch) route::with_method(Some(route::Method::Patch), args, input)
} }
/// Creates resource handler, allowing multiple HTTP method guards. /// Creates resource handler, allowing multiple HTTP method guards.
@ -155,7 +155,7 @@ pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
/// - `wrap="Middleware"` - Registers a resource middleware. /// - `wrap="Middleware"` - Registers a resource middleware.
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn route(args: TokenStream, input: TokenStream) -> TokenStream { pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
route::generate(args, input, route::GuardType::Multi) route::with_method(None, args, input)
} }
/// Marks async main function as the actix system entry-point. /// Marks async main function as the actix system entry-point.

View File

@ -21,7 +21,7 @@ impl ToTokens for ResourceType {
} }
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub enum GuardType { pub enum Method {
Get, Get,
Post, Post,
Put, Put,
@ -31,47 +31,45 @@ pub enum GuardType {
Options, Options,
Trace, Trace,
Patch, Patch,
Multi,
} }
impl GuardType { impl Method {
fn as_str(&self) -> &'static str { fn as_str(&self) -> &'static str {
match self { match self {
GuardType::Get => "Get", Method::Get => "Get",
GuardType::Post => "Post", Method::Post => "Post",
GuardType::Put => "Put", Method::Put => "Put",
GuardType::Delete => "Delete", Method::Delete => "Delete",
GuardType::Head => "Head", Method::Head => "Head",
GuardType::Connect => "Connect", Method::Connect => "Connect",
GuardType::Options => "Options", Method::Options => "Options",
GuardType::Trace => "Trace", Method::Trace => "Trace",
GuardType::Patch => "Patch", Method::Patch => "Patch",
GuardType::Multi => "Multi",
} }
} }
} }
impl ToTokens for GuardType { impl ToTokens for Method {
fn to_tokens(&self, stream: &mut TokenStream2) { fn to_tokens(&self, stream: &mut TokenStream2) {
let ident = Ident::new(self.as_str(), Span::call_site()); let ident = Ident::new(self.as_str(), Span::call_site());
stream.append(ident); stream.append(ident);
} }
} }
impl TryFrom<&syn::LitStr> for GuardType { impl TryFrom<&syn::LitStr> for Method {
type Error = syn::Error; type Error = syn::Error;
fn try_from(value: &syn::LitStr) -> Result<Self, Self::Error> { fn try_from(value: &syn::LitStr) -> Result<Self, Self::Error> {
match value.value().as_str() { match value.value().as_str() {
"CONNECT" => Ok(GuardType::Connect), "CONNECT" => Ok(Method::Connect),
"DELETE" => Ok(GuardType::Delete), "DELETE" => Ok(Method::Delete),
"GET" => Ok(GuardType::Get), "GET" => Ok(Method::Get),
"HEAD" => Ok(GuardType::Head), "HEAD" => Ok(Method::Head),
"OPTIONS" => Ok(GuardType::Options), "OPTIONS" => Ok(Method::Options),
"PATCH" => Ok(GuardType::Patch), "PATCH" => Ok(Method::Patch),
"POST" => Ok(GuardType::Post), "POST" => Ok(Method::Post),
"PUT" => Ok(GuardType::Put), "PUT" => Ok(Method::Put),
"TRACE" => Ok(GuardType::Trace), "TRACE" => Ok(Method::Trace),
_ => Err(syn::Error::new_spanned( _ => Err(syn::Error::new_spanned(
value, value,
&format!("Unexpected HTTP Method: `{}`", value.value()), &format!("Unexpected HTTP Method: `{}`", value.value()),
@ -84,15 +82,14 @@ struct Args {
path: syn::LitStr, path: syn::LitStr,
guards: Vec<Ident>, guards: Vec<Ident>,
wrappers: Vec<syn::Type>, wrappers: Vec<syn::Type>,
methods: HashSet<GuardType>, methods: HashSet<Method>,
} }
impl Args { impl Args {
fn new(args: AttributeArgs) -> syn::Result<Self> { fn new(args: AttributeArgs, mut methods: HashSet<Method>) -> syn::Result<Self> {
let mut path = None; let mut path = None;
let mut guards = Vec::new(); let mut guards = Vec::new();
let mut wrappers = Vec::new(); let mut wrappers = Vec::new();
let mut methods = HashSet::new();
for arg in args { for arg in args {
match arg { match arg {
NestedMeta::Lit(syn::Lit::Str(lit)) => match path { NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
@ -127,8 +124,8 @@ impl Args {
} }
} else if nv.path.is_ident("method") { } else if nv.path.is_ident("method") {
if let syn::Lit::Str(ref lit) = nv.lit { if let syn::Lit::Str(ref lit) = nv.lit {
let guard = GuardType::try_from(lit)?; let method = Method::try_from(lit)?;
if !methods.insert(guard) { if !methods.insert(method) {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
&nv.lit, &nv.lit,
&format!( &format!(
@ -169,7 +166,6 @@ pub struct Route {
args: Args, args: Args,
ast: syn::ItemFn, ast: syn::ItemFn,
resource_type: ResourceType, resource_type: ResourceType,
guard: GuardType,
} }
fn guess_resource_type(typ: &syn::Type) -> ResourceType { fn guess_resource_type(typ: &syn::Type) -> ResourceType {
@ -198,23 +194,26 @@ impl Route {
pub fn new( pub fn new(
args: AttributeArgs, args: AttributeArgs,
input: TokenStream, input: TokenStream,
guard: GuardType, method: Option<Method>,
) -> syn::Result<Self> { ) -> syn::Result<Self> {
if args.is_empty() { if args.is_empty() {
return Err(syn::Error::new( return Err(syn::Error::new(
Span::call_site(), Span::call_site(),
format!( format!(
r#"invalid server definition, expected #[{}("<some path>")]"#, r#"invalid service definition, expected #[{}("<some path>")]"#,
guard.as_str().to_ascii_lowercase() method
.map(|it| it.as_str())
.unwrap_or("route")
.to_ascii_lowercase()
), ),
)); ));
} }
let ast: syn::ItemFn = syn::parse(input)?; let ast: syn::ItemFn = syn::parse(input)?;
let name = ast.sig.ident.clone(); let name = ast.sig.ident.clone();
let args = Args::new(args)?; let methods = method.into_iter().collect();
let args = Args::new(args, methods)?;
if guard == GuardType::Multi && args.methods.is_empty() { if args.methods.is_empty() {
return Err(syn::Error::new( return Err(syn::Error::new(
Span::call_site(), Span::call_site(),
"The #[route(..)] macro requires at least one `method` attribute", "The #[route(..)] macro requires at least one `method` attribute",
@ -240,7 +239,6 @@ impl Route {
args, args,
ast, ast,
resource_type, resource_type,
guard,
}) })
} }
} }
@ -249,7 +247,6 @@ impl ToTokens for Route {
fn to_tokens(&self, output: &mut TokenStream2) { fn to_tokens(&self, output: &mut TokenStream2) {
let Self { let Self {
name, name,
guard,
ast, ast,
args: args:
Args { Args {
@ -261,21 +258,22 @@ impl ToTokens for Route {
resource_type, resource_type,
} = self; } = self;
let resource_name = name.to_string(); let resource_name = name.to_string();
let mut methods = methods.iter(); let method_guards = {
let mut others = methods.iter();
let method_guards = if *guard == GuardType::Multi {
// unwrapping since length is checked to be at least one // unwrapping since length is checked to be at least one
let first = methods.next().unwrap(); let first = others.next().unwrap();
quote! { if methods.len() > 1 {
.guard( quote! {
actix_web::guard::Any(actix_web::guard::#first()) .guard(
#(.or(actix_web::guard::#methods()))* actix_web::guard::Any(actix_web::guard::#first())
) #(.or(actix_web::guard::#others()))*
} )
} else { }
quote! { } else {
.guard(actix_web::guard::#guard()) quote! {
.guard(actix_web::guard::#first())
}
} }
}; };
@ -302,13 +300,13 @@ impl ToTokens for Route {
} }
} }
pub(crate) fn generate( pub(crate) fn with_method(
method: Option<Method>,
args: TokenStream, args: TokenStream,
input: TokenStream, input: TokenStream,
guard: GuardType,
) -> TokenStream { ) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
match Route::new(args, input, guard) { match Route::new(args, input, method) {
Ok(route) => route.into_token_stream().into(), Ok(route) => route.into_token_stream().into(),
Err(err) => err.to_compile_error().into(), Err(err) => err.to_compile_error().into(),
} }