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)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// ## Usage
|
||||
|
|
|
@ -17,7 +17,7 @@ impl ToTokens for ResourceType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GuardType {
|
||||
Get,
|
||||
Post,
|
||||
|
@ -28,6 +28,7 @@ pub enum GuardType {
|
|||
Options,
|
||||
Trace,
|
||||
Patch,
|
||||
Multi,
|
||||
}
|
||||
|
||||
impl GuardType {
|
||||
|
@ -42,6 +43,7 @@ impl GuardType {
|
|||
GuardType::Options => "Options",
|
||||
GuardType::Trace => "Trace",
|
||||
GuardType::Patch => "Patch",
|
||||
GuardType::Multi => "Multi",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +59,7 @@ struct Args {
|
|||
path: syn::LitStr,
|
||||
guards: Vec<Ident>,
|
||||
wrappers: Vec<syn::Type>,
|
||||
methods: Vec<GuardType>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
|
@ -64,6 +67,7 @@ impl Args {
|
|||
let mut path = None;
|
||||
let mut guards = Vec::new();
|
||||
let mut wrappers = Vec::new();
|
||||
let mut methods = Vec::new();
|
||||
for arg in args {
|
||||
match arg {
|
||||
NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
|
||||
|
@ -96,6 +100,36 @@ impl Args {
|
|||
"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 {
|
||||
return Err(syn::Error::new_spanned(
|
||||
nv.path,
|
||||
|
@ -112,6 +146,7 @@ impl Args {
|
|||
path: path.unwrap(),
|
||||
guards,
|
||||
wrappers,
|
||||
methods,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +201,13 @@ impl Route {
|
|||
|
||||
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() {
|
||||
ResourceType::Async
|
||||
} else {
|
||||
|
@ -201,25 +243,47 @@ impl ToTokens for Route {
|
|||
path,
|
||||
guards,
|
||||
wrappers,
|
||||
methods,
|
||||
},
|
||||
resource_type,
|
||||
} = self;
|
||||
let resource_name = name.to_string();
|
||||
let stream = quote! {
|
||||
#[allow(non_camel_case_types, missing_docs)]
|
||||
pub struct #name;
|
||||
let stream = if guard != &GuardType::Multi {
|
||||
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::#guard())
|
||||
#(.guard(actix_web::guard::fn_guard(#guards)))*
|
||||
#(.wrap(#wrappers))*
|
||||
.#resource_type(#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::#guard())
|
||||
#(.guard(actix_web::guard::fn_guard(#guards)))*
|
||||
#(.wrap(#wrappers))*
|
||||
.#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>>);
|
||||
|
||||
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
|
||||
pub fn or<F: Guard + 'static>(mut self, guard: F) -> Self {
|
||||
self.0.push(Box::new(guard));
|
||||
|
|
Loading…
Reference in New Issue