1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 18:06:23 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
1ca9d87f0a prep actix-web-codegen release 2019-10-14 21:35:53 +06:00
967f965405 Update syn & quote to 1.0 (#1133)
* chore(actix-web-codegen): Upgrade syn and quote to 1.0

* feat(actix-web-codegen): Generate better error message

* doc(actix-web-codegen): Update CHANGES.md

* fix: Build with stable rust
2019-10-14 21:34:17 +06:00
062e51e8ce prep actix-file release 2019-10-14 21:26:26 +06:00
a48e616def feat(files): add possibility to redirect to slash-ended path (#1134)
When accessing to a folder without a final slash, the index file will be loaded ok, but if it has
references (like a css or an image in an html file) these resources won't be loaded correctly if
they are using relative paths. In order to solve this, this PR adds the possibility to detect
folders without a final slash and make a 302 redirect to mitigate this issue. The behavior is off by
default. We're adding a new method called `redirect_to_slash_directory` which can be used to enable
this behavior.
2019-10-14 21:23:15 +06:00
effa96f5e4 Removed httpcode 'MovedPermanenty'. (#1128) 2019-10-12 06:45:12 +06:00
cc0b4be5b7 Fix typo in response.rs body() comment (#1126)
Fixes https://github.com/actix/actix-web/issues/1125
2019-10-09 19:11:55 +06:00
9 changed files with 238 additions and 127 deletions

View File

@ -1,5 +1,9 @@
# Changes # Changes
## [0.1.6] - 2019-10-14
* Add option to redirect to a slash-ended path `Files` #1132
## [0.1.5] - 2019-10-08 ## [0.1.5] - 2019-10-08
* Bump up `mime_guess` crate version to 2.0.1 * Bump up `mime_guess` crate version to 2.0.1
@ -8,17 +12,14 @@
* Allow user defined request guards for `Files` #1113 * Allow user defined request guards for `Files` #1113
## [0.1.4] - 2019-07-20 ## [0.1.4] - 2019-07-20
* Allow to disable `Content-Disposition` header #686 * Allow to disable `Content-Disposition` header #686
## [0.1.3] - 2019-06-28 ## [0.1.3] - 2019-06-28
* Do not set `Content-Length` header, let actix-http set it #930 * Do not set `Content-Length` header, let actix-http set it #930
## [0.1.2] - 2019-06-13 ## [0.1.2] - 2019-06-13
* Content-Length is 0 for NamedFile HEAD request #914 * Content-Length is 0 for NamedFile HEAD request #914

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.1.5" version = "0.1.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"

View File

@ -233,6 +233,7 @@ pub struct Files {
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
redirect_to_slash: bool,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
@ -246,6 +247,7 @@ impl Clone for Files {
directory: self.directory.clone(), directory: self.directory.clone(),
index: self.index.clone(), index: self.index.clone(),
show_index: self.show_index, show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: self.default.clone(), default: self.default.clone(),
renderer: self.renderer.clone(), renderer: self.renderer.clone(),
file_flags: self.file_flags, file_flags: self.file_flags,
@ -273,6 +275,7 @@ impl Files {
directory: dir, directory: dir,
index: None, index: None,
show_index: false, show_index: false,
redirect_to_slash: false,
default: Rc::new(RefCell::new(None)), default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing), renderer: Rc::new(directory_listing),
mime_override: None, mime_override: None,
@ -289,6 +292,14 @@ impl Files {
self self
} }
/// Redirects to a slash-ended path when browsing a directory.
///
/// By default never redirect.
pub fn redirect_to_slash_directory(mut self) -> Self {
self.redirect_to_slash = true;
self
}
/// Set custom directory renderer /// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where where
@ -389,10 +400,10 @@ impl HttpServiceFactory for Files {
} }
impl NewService for Files { impl NewService for Files {
type Config = ();
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Config = ();
type Service = FilesService; type Service = FilesService;
type InitError = (); type InitError = ();
type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>; type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>;
@ -402,6 +413,7 @@ impl NewService for Files {
directory: self.directory.clone(), directory: self.directory.clone(),
index: self.index.clone(), index: self.index.clone(),
show_index: self.show_index, show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: None, default: None,
renderer: self.renderer.clone(), renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(), mime_override: self.mime_override.clone(),
@ -429,6 +441,7 @@ pub struct FilesService {
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
redirect_to_slash: bool,
default: Option<HttpService>, default: Option<HttpService>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
@ -502,6 +515,16 @@ impl Service for FilesService {
if path.is_dir() { if path.is_dir() {
if let Some(ref redir_index) = self.index { if let Some(ref redir_index) = self.index {
if self.redirect_to_slash && !req.path().ends_with('/') {
let redirect_to = format!("{}/", req.path());
return Either::A(ok(req.into_response(
HttpResponse::Found()
.header(header::LOCATION, redirect_to)
.body("")
.into_body(),
)));
}
let path = path.join(redir_index); let path = path.join(redir_index);
match NamedFile::open(path) { match NamedFile::open(path) {
@ -1169,6 +1192,34 @@ mod tests {
assert!(format!("{:?}", bytes).contains("/tests/test.png")); assert!(format!("{:?}", bytes).contains("/tests/test.png"));
} }
#[test]
fn test_redirect_to_slash_directory() {
// should not redirect if no index
let mut srv = test::init_service(
App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
);
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
// should redirect if index present
let mut srv = test::init_service(
App::new().service(
Files::new("/", ".")
.index_file("test.png")
.redirect_to_slash_directory(),
),
);
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::FOUND);
// should not redirect if the path is wrong
let req = TestRequest::with_uri("/not_existing").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[test] #[test]
fn test_static_files_bad_directory() { fn test_static_files_bad_directory() {
let _st: Files = Files::new("/", "missing"); let _st: Files = Files::new("/", "missing");

View File

@ -29,7 +29,6 @@ impl Response {
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(Found, StatusCode::FOUND); STATIC_RESP!(Found, StatusCode::FOUND);
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER);

View File

@ -194,7 +194,7 @@ impl<B> Response<B> {
self.head.extensions.borrow_mut() self.head.extensions.borrow_mut()
} }
/// Get body os this response /// Get body of this response
#[inline] #[inline]
pub fn body(&self) -> &ResponseBody<B> { pub fn body(&self) -> &ResponseBody<B> {
&self.body &self.body

View File

@ -1,5 +1,11 @@
# Changes # Changes
## [0.1.3] - 2019-10-14
* Bump up `syn` & `quote` to 1.0
* Provide better error message
## [0.1.2] - 2019-06-04 ## [0.1.2] - 2019-06-04
* Add macros for head, options, trace, connect and patch http methods * Add macros for head, options, trace, connect and patch http methods

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.1.2" version = "0.1.3"
description = "Actix web proc macros" description = "Actix web proc macros"
readme = "README.md" readme = "README.md"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
@ -12,8 +12,9 @@ workspace = ".."
proc-macro = true proc-macro = true
[dependencies] [dependencies]
quote = "0.6.12" quote = "1"
syn = { version = "0.15.34", features = ["full", "parsing", "extra-traits"] } syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0" } actix-web = { version = "1.0.0" }

View File

@ -58,7 +58,10 @@ use syn::parse_macro_input;
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Get); let gen = match route::Route::new(args, input, route::GuardType::Get) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -70,7 +73,10 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Post); let gen = match route::Route::new(args, input, route::GuardType::Post) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -82,7 +88,10 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Put); let gen = match route::Route::new(args, input, route::GuardType::Put) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -94,7 +103,10 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Delete); let gen = match route::Route::new(args, input, route::GuardType::Delete) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -106,7 +118,10 @@ pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Head); let gen = match route::Route::new(args, input, route::GuardType::Head) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -118,7 +133,10 @@ pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Connect); let gen = match route::Route::new(args, input, route::GuardType::Connect) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -130,7 +148,10 @@ pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Options); let gen = match route::Route::new(args, input, route::GuardType::Options) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -142,7 +163,10 @@ pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Trace); let gen = match route::Route::new(args, input, route::GuardType::Trace) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -154,6 +178,9 @@ pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Patch); let gen = match route::Route::new(args, input, route::GuardType::Patch) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }

View File

@ -1,21 +1,23 @@
extern crate proc_macro; extern crate proc_macro;
use std::fmt;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{AttributeArgs, Ident, NestedMeta};
enum ResourceType { enum ResourceType {
Async, Async,
Sync, Sync,
} }
impl fmt::Display for ResourceType { impl ToTokens for ResourceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn to_tokens(&self, stream: &mut TokenStream2) {
match *self { let ident = match self {
ResourceType::Async => write!(f, "to_async"), ResourceType::Async => "to_async",
ResourceType::Sync => write!(f, "to"), ResourceType::Sync => "to",
} };
let ident = Ident::new(ident, Span::call_site());
stream.append(ident);
} }
} }
@ -32,63 +34,89 @@ pub enum GuardType {
Patch, Patch,
} }
impl fmt::Display for GuardType { impl GuardType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn as_str(&self) -> &'static str {
match *self { match self {
GuardType::Get => write!(f, "Get"), GuardType::Get => "Get",
GuardType::Post => write!(f, "Post"), GuardType::Post => "Post",
GuardType::Put => write!(f, "Put"), GuardType::Put => "Put",
GuardType::Delete => write!(f, "Delete"), GuardType::Delete => "Delete",
GuardType::Head => write!(f, "Head"), GuardType::Head => "Head",
GuardType::Connect => write!(f, "Connect"), GuardType::Connect => "Connect",
GuardType::Options => write!(f, "Options"), GuardType::Options => "Options",
GuardType::Trace => write!(f, "Trace"), GuardType::Trace => "Trace",
GuardType::Patch => write!(f, "Patch"), GuardType::Patch => "Patch",
} }
} }
} }
pub struct Args { impl ToTokens for GuardType {
name: syn::Ident, fn to_tokens(&self, stream: &mut TokenStream2) {
path: String, let ident = self.as_str();
ast: syn::ItemFn, let ident = Ident::new(ident, Span::call_site());
resource_type: ResourceType, stream.append(ident);
pub guard: GuardType, }
pub extra_guards: Vec<String>,
} }
impl fmt::Display for Args { struct Args {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { path: syn::LitStr,
let ast = &self.ast; guards: Vec<Ident>,
let guards = format!(".guard(actix_web::guard::{}())", self.guard); }
let guards = self.extra_guards.iter().fold(guards, |acc, val| {
format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)
});
write!( impl Args {
f, fn new(args: AttributeArgs) -> syn::Result<Self> {
" let mut path = None;
#[allow(non_camel_case_types)] let mut guards = Vec::new();
pub struct {name}; for arg in args {
match arg {
impl actix_web::dev::HttpServiceFactory for {name} {{ NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
fn register(self, config: &mut actix_web::dev::AppService) {{ None => {
{ast} path = Some(lit);
}
let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name}); _ => {
return Err(syn::Error::new_spanned(
actix_web::dev::HttpServiceFactory::register(resource, config) lit,
}} "Multiple paths specified! Should be only one!",
}}", ));
name = self.name, }
ast = quote!(#ast), },
path = self.path, NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
guards = guards, if nv.path.is_ident("guard") {
to = self.resource_type 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,
})
} }
} }
pub struct Route {
name: syn::Ident,
args: Args,
ast: syn::ItemFn,
resource_type: ResourceType,
guard: GuardType,
}
fn guess_resource_type(typ: &syn::Type) -> ResourceType { fn guess_resource_type(typ: &syn::Type) -> ResourceType {
let mut guess = ResourceType::Sync; let mut guess = ResourceType::Sync;
@ -111,75 +139,73 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType {
guess guess
} }
impl Args { impl Route {
pub fn new(args: &[syn::NestedMeta], input: TokenStream, guard: GuardType) -> Self { pub fn new(
args: AttributeArgs,
input: TokenStream,
guard: GuardType,
) -> syn::Result<Self> {
if args.is_empty() { if args.is_empty() {
panic!( return Err(syn::Error::new(
"invalid server definition, expected: #[{}(\"some path\")]", Span::call_site(),
guard format!(
); r#"invalid server definition, expected #[{}("<some path>")]"#,
guard.as_str().to_ascii_lowercase()
),
));
} }
let ast: syn::ItemFn = syn::parse(input)?;
let name = ast.sig.ident.clone();
let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function"); let args = Args::new(args)?;
let name = ast.ident.clone();
let mut extra_guards = Vec::new(); let resource_type = if ast.sig.asyncness.is_some() {
let mut path = None;
for arg in args {
match arg {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
if path.is_some() {
panic!("Multiple paths specified! Should be only one!")
}
let fname = quote!(#fname).to_string();
path = Some(fname.as_str()[1..fname.len() - 1].to_owned())
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => {
match ident.ident.to_string().to_lowercase().as_str() {
"guard" => match ident.lit {
syn::Lit::Str(ref text) => extra_guards.push(text.value()),
_ => panic!("Attribute guard expects literal string!"),
},
attr => panic!(
"Unknown attribute key is specified: {}. Allowed: guard",
attr
),
}
}
attr => panic!("Unknown attribute{:?}", attr),
}
}
let resource_type = if ast.asyncness.is_some() {
ResourceType::Async ResourceType::Async
} else { } else {
match ast.decl.output { match ast.sig.output {
syn::ReturnType::Default => panic!( syn::ReturnType::Default => {
"Function {} has no return type. Cannot be used as handler", return Err(syn::Error::new_spanned(
name ast,
), "Function has no return type. Cannot be used as handler",
));
}
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
} }
}; };
let path = path.unwrap(); Ok(Self {
Self {
name, name,
path, args,
ast, ast,
resource_type, resource_type,
guard, guard,
extra_guards, })
}
} }
pub fn generate(&self) -> TokenStream { pub fn generate(&self) -> TokenStream {
let text = self.to_string(); 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;
match text.parse() { impl actix_web::dev::HttpServiceFactory for #name {
Ok(res) => res, fn register(self, config: &mut actix_web::dev::AppService) {
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text), #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()
} }
} }