mirror of
https://github.com/fafhrd91/actix-web
synced 2025-02-12 08:05:34 +01:00
* add scope proc macro * Update scope macro code to work with current HttpServiceFactory * started some test code * add some unit tests * code formatting cleanup * add another test for combining and calling 2 scopes * format code with formatter * Update actix-web-codegen/src/lib.rs with comment documentation fix Co-authored-by: oliver <151407407+kwfn@users.noreply.github.com> * work in progress. revised procedural macro to change othe macro call * add tests again. refactor nested code. * clean up code. fix bugs with route and method attributes with parameters * clean up for rust fmt * clean up for rust fmt * fix out of date comment for scope macro * sync to master branch by adding test_wrap * needed to format code * test: split out scope tests * test: add negative tests * chore: move imports back inside (?) * docs: tweak scope docs * fix: prevent trailing slashes in scope prefixes * chore: address clippy lints --------- Co-authored-by: oliver <151407407+kwfn@users.noreply.github.com> Co-authored-by: Rob Ede <robjtede@icloud.com>
104 lines
3.5 KiB
Rust
104 lines
3.5 KiB
Rust
use proc_macro::TokenStream;
|
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
|
use quote::{quote, ToTokens as _};
|
|
|
|
use crate::{
|
|
input_and_compile_error,
|
|
route::{MethodType, RouteArgs},
|
|
};
|
|
|
|
pub fn with_scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
|
match with_scope_inner(args, input.clone()) {
|
|
Ok(stream) => stream,
|
|
Err(err) => input_and_compile_error(input, err),
|
|
}
|
|
}
|
|
|
|
fn with_scope_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
|
|
if args.is_empty() {
|
|
return Err(syn::Error::new(
|
|
Span::call_site(),
|
|
"missing arguments for scope macro, expected: #[scope(\"/prefix\")]",
|
|
));
|
|
}
|
|
|
|
let scope_prefix = syn::parse::<syn::LitStr>(args.clone()).map_err(|err| {
|
|
syn::Error::new(
|
|
err.span(),
|
|
"argument to scope macro is not a string literal, expected: #[scope(\"/prefix\")]",
|
|
)
|
|
})?;
|
|
|
|
let scope_prefix_value = scope_prefix.value();
|
|
|
|
if scope_prefix_value.ends_with('/') {
|
|
// trailing slashes cause non-obvious problems
|
|
// it's better to point them out to developers rather than
|
|
|
|
return Err(syn::Error::new(
|
|
scope_prefix.span(),
|
|
"scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes",
|
|
));
|
|
}
|
|
|
|
let mut module = syn::parse::<syn::ItemMod>(input).map_err(|err| {
|
|
syn::Error::new(err.span(), "#[scope] macro must be attached to a module")
|
|
})?;
|
|
|
|
// modify any routing macros (method or route[s]) attached to
|
|
// functions by prefixing them with this scope macro's argument
|
|
if let Some((_, items)) = &mut module.content {
|
|
for item in items {
|
|
if let syn::Item::Fn(fun) = item {
|
|
fun.attrs = fun
|
|
.attrs
|
|
.iter()
|
|
.map(|attr| modify_attribute_with_scope(attr, &scope_prefix_value))
|
|
.collect();
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(module.to_token_stream().into())
|
|
}
|
|
|
|
/// Checks if the attribute is a method type and has a route path, then modifies it.
|
|
fn modify_attribute_with_scope(attr: &syn::Attribute, scope_path: &str) -> syn::Attribute {
|
|
match (attr.parse_args::<RouteArgs>(), attr.clone().meta) {
|
|
(Ok(route_args), syn::Meta::List(meta_list)) if has_allowed_methods_in_scope(attr) => {
|
|
let modified_path = format!("{}{}", scope_path, route_args.path.value());
|
|
|
|
let options_tokens: Vec<TokenStream2> = route_args
|
|
.options
|
|
.iter()
|
|
.map(|option| {
|
|
quote! { ,#option }
|
|
})
|
|
.collect();
|
|
|
|
let combined_options_tokens: TokenStream2 =
|
|
options_tokens
|
|
.into_iter()
|
|
.fold(TokenStream2::new(), |mut acc, ts| {
|
|
acc.extend(std::iter::once(ts));
|
|
acc
|
|
});
|
|
|
|
syn::Attribute {
|
|
meta: syn::Meta::List(syn::MetaList {
|
|
tokens: quote! { #modified_path #combined_options_tokens },
|
|
..meta_list.clone()
|
|
}),
|
|
..attr.clone()
|
|
}
|
|
}
|
|
_ => attr.clone(),
|
|
}
|
|
}
|
|
|
|
fn has_allowed_methods_in_scope(attr: &syn::Attribute) -> bool {
|
|
MethodType::from_path(attr.path()).is_ok()
|
|
|| attr.path().is_ident("route")
|
|
|| attr.path().is_ident("ROUTE")
|
|
}
|