mirror of https://github.com/fafhrd91/actix-web
Provide attribute macro for multiple HTTP methods
What -- Define a new `route` attribute macro that supports defining multiple HTTP methods to routed to (handled by) a single handler. The attribute macro syntax looks like this ```rust use actix_web::route; async fn multi_methods() -> &'static str { "Hello world!\r\n" } ``` How -- This implementation extends the [`GuardType`][1] enum in actix-web-codegen to have a new `GuardType::Multi` variant that denotes when multiple method guards are used. A new `methods` attribute in the `route` attribute macro provides a comma-separated list of HTTP methods to provide guard for. The code parses the methods list, matches them to the respective `GuardType` and uses the `AnyGuard` struct to combine them together. A constructor method for [`AnyGuard`][2] is added to support this. The generated code looks like this: ```rust pub struct multi_methods; impl actix_web::dev::HttpServiceFactory for multi_methods { fn register(self, __config: &mut actix_web::dev::AppService) { ¦ async fn multi_methods() -> &'static str { ¦ ¦ "Hello world!\r\n" ¦ } ¦ let __resource = actix_web::Resource::new("/multi") ¦ ¦ .name("multi_methods") ¦ ¦ .guard(actix_web:💂:AnyGuard::new(<[_]>::into_vec(box [ ¦ ¦ ¦ Box::new(actix_web:💂:Get()), ¦ ¦ ¦ Box::new(actix_web:💂:Post()), ¦ ¦ ]))) ¦ ¦ .to(multi_methods); ¦ actix_web::dev::HttpServiceFactory::register(__resource, __config) } } ``` **NOTE: This is my first attempt that implementing this feature. Feedback and mentorship is highly welcome to improve it :-)** Why -- This fixes https://github.com/actix/actix-web/issues/1360 [1]: https://github.com/actix/actix-web/blob/master/actix-web-codegen/src/route.rs#L21 [2]: https://github.com/actix/actix-web/blob/master/src/guard.rs#L104s
This commit is contained in:
parent
d707704556
commit
31348a2339
|
@ -141,6 +141,20 @@ pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
route::generate(args, input, route::GuardType::Patch)
|
route::generate(args, input, route::GuardType::Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates route handler with Multiple HTTP methods guards.
|
||||||
|
///
|
||||||
|
/// Syntax: `#[route("path"[, attributes])]`
|
||||||
|
///
|
||||||
|
/// ## Attributes
|
||||||
|
/// - `"path"` - Raw literal string with path for which to register handler. Mandatory.
|
||||||
|
/// - `methods="HTTP_METHOD_1,HTTP_METHOD_2"` - Registers HTTP methods to provide guards for.
|
||||||
|
/// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`
|
||||||
|
/// - `wrap="Middleware"` - Registers a resource middleware.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
route::generate(args, input, route::GuardType::Multi)
|
||||||
|
}
|
||||||
|
|
||||||
/// Marks async main function as the actix system entry-point.
|
/// Marks async main function as the actix system entry-point.
|
||||||
///
|
///
|
||||||
/// ## Usage
|
/// ## Usage
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl ToTokens for ResourceType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum GuardType {
|
pub enum GuardType {
|
||||||
Get,
|
Get,
|
||||||
Post,
|
Post,
|
||||||
|
@ -28,6 +28,7 @@ pub enum GuardType {
|
||||||
Options,
|
Options,
|
||||||
Trace,
|
Trace,
|
||||||
Patch,
|
Patch,
|
||||||
|
Multi,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GuardType {
|
impl GuardType {
|
||||||
|
@ -42,6 +43,7 @@ impl GuardType {
|
||||||
GuardType::Options => "Options",
|
GuardType::Options => "Options",
|
||||||
GuardType::Trace => "Trace",
|
GuardType::Trace => "Trace",
|
||||||
GuardType::Patch => "Patch",
|
GuardType::Patch => "Patch",
|
||||||
|
GuardType::Multi => "Multi",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +59,7 @@ struct Args {
|
||||||
path: syn::LitStr,
|
path: syn::LitStr,
|
||||||
guards: Vec<Ident>,
|
guards: Vec<Ident>,
|
||||||
wrappers: Vec<syn::Type>,
|
wrappers: Vec<syn::Type>,
|
||||||
|
methods: Vec<GuardType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
|
@ -64,6 +67,7 @@ impl Args {
|
||||||
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 = Vec::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 {
|
||||||
|
@ -96,6 +100,36 @@ impl Args {
|
||||||
"Attribute wrap expects type",
|
"Attribute wrap expects type",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
} else if nv.path.is_ident("methods") {
|
||||||
|
if let syn::Lit::Str(ref lit) = nv.lit {
|
||||||
|
for meth in lit.value().split(',') {
|
||||||
|
match meth.to_uppercase().as_str() {
|
||||||
|
"CONNECT" => methods.push(GuardType::Connect),
|
||||||
|
"DELETE" => methods.push(GuardType::Delete),
|
||||||
|
"GET" => methods.push(GuardType::Get),
|
||||||
|
"HEAD" => methods.push(GuardType::Head),
|
||||||
|
"OPTIONS" => methods.push(GuardType::Options),
|
||||||
|
"PATCH" => methods.push(GuardType::Patch),
|
||||||
|
"POST" => methods.push(GuardType::Post),
|
||||||
|
"PUT" => methods.push(GuardType::Put),
|
||||||
|
"TRACE" => methods.push(GuardType::Trace),
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.lit,
|
||||||
|
&format!(
|
||||||
|
"Unexpected HTTP Method: `{}`",
|
||||||
|
meth
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.lit,
|
||||||
|
"Attribute methods expects literal string!",
|
||||||
|
));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
nv.path,
|
nv.path,
|
||||||
|
@ -112,6 +146,7 @@ impl Args {
|
||||||
path: path.unwrap(),
|
path: path.unwrap(),
|
||||||
guards,
|
guards,
|
||||||
wrappers,
|
wrappers,
|
||||||
|
methods,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,6 +201,13 @@ impl Route {
|
||||||
|
|
||||||
let args = Args::new(args)?;
|
let args = Args::new(args)?;
|
||||||
|
|
||||||
|
if guard == GuardType::Multi && args.methods.is_empty() {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"The #[route(..)] macro requires the `methods` attribute!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let resource_type = if ast.sig.asyncness.is_some() {
|
let resource_type = if ast.sig.asyncness.is_some() {
|
||||||
ResourceType::Async
|
ResourceType::Async
|
||||||
} else {
|
} else {
|
||||||
|
@ -201,25 +243,47 @@ impl ToTokens for Route {
|
||||||
path,
|
path,
|
||||||
guards,
|
guards,
|
||||||
wrappers,
|
wrappers,
|
||||||
|
methods,
|
||||||
},
|
},
|
||||||
resource_type,
|
resource_type,
|
||||||
} = self;
|
} = self;
|
||||||
let resource_name = name.to_string();
|
let resource_name = name.to_string();
|
||||||
let stream = quote! {
|
let stream = if guard != &GuardType::Multi {
|
||||||
#[allow(non_camel_case_types, missing_docs)]
|
quote! {
|
||||||
pub struct #name;
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
|
pub struct #name;
|
||||||
|
|
||||||
impl actix_web::dev::HttpServiceFactory for #name {
|
impl actix_web::dev::HttpServiceFactory for #name {
|
||||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||||
#ast
|
#ast
|
||||||
let __resource = actix_web::Resource::new(#path)
|
let __resource = actix_web::Resource::new(#path)
|
||||||
.name(#resource_name)
|
.name(#resource_name)
|
||||||
.guard(actix_web::guard::#guard())
|
.guard(actix_web::guard::#guard())
|
||||||
#(.guard(actix_web::guard::fn_guard(#guards)))*
|
#(.guard(actix_web::guard::fn_guard(#guards)))*
|
||||||
#(.wrap(#wrappers))*
|
#(.wrap(#wrappers))*
|
||||||
.#resource_type(#name);
|
.#resource_type(#name);
|
||||||
|
|
||||||
actix_web::dev::HttpServiceFactory::register(__resource, __config)
|
actix_web::dev::HttpServiceFactory::register(__resource, __config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
|
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)
|
||||||
|
.name(#resource_name)
|
||||||
|
.guard(actix_web::guard::AnyGuard::new(vec![#(Box::new(actix_web::guard::#methods())),*]))
|
||||||
|
#(.guard(actix_web::guard::fn_guard(#guards)))*
|
||||||
|
#(.wrap(#wrappers))*
|
||||||
|
.#resource_type(#name);
|
||||||
|
|
||||||
|
actix_web::dev::HttpServiceFactory::register(__resource, __config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -104,6 +104,10 @@ pub fn Any<F: Guard + 'static>(guard: F) -> AnyGuard {
|
||||||
pub struct AnyGuard(Vec<Box<dyn Guard>>);
|
pub struct AnyGuard(Vec<Box<dyn Guard>>);
|
||||||
|
|
||||||
impl AnyGuard {
|
impl AnyGuard {
|
||||||
|
/// Create AnyGuard from a vector of Guards
|
||||||
|
pub fn new(guards: Vec<Box<dyn Guard>>) -> Self {
|
||||||
|
AnyGuard(guards)
|
||||||
|
}
|
||||||
/// Add guard to a list of guards to check
|
/// Add guard to a list of guards to check
|
||||||
pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
|
pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
|
||||||
self.0.push(Box::new(guard));
|
self.0.push(Box::new(guard));
|
||||||
|
|
Loading…
Reference in New Issue