diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index f5b17ed6c..87ba92bfb 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -1,11 +1,11 @@ -use std::{collections::HashSet}; +use std::collections::HashSet; use actix_router::ResourceDef; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token, ItemFn}; -use syn::Item::Fn; // todo: cleanup +use syn::Item::Fn; +use syn::{punctuated::Punctuated, Ident, ItemFn, LitStr, Path, Token}; // todo: cleanup #[derive(Debug)] pub struct RouteArgs { @@ -556,72 +556,83 @@ fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStrea item } +// todo: regular new scoped path test... // todo: test with enums in module... +// todo: test with different namespaces... // todo: test with multiple get/post methods... // todo: test with doc strings in attributes... // todo: clean up the nested tree of death, tell it about the input_and_compile_error convenience function... // todo: add in the group functions for convenient handling of group... // todo: see if can cleanup the Attribute with a clone plus one field change... +// todo: #[actix_web::get("/test")] the namespace is messing matching up pub fn with_scope(args: TokenStream, input: TokenStream) -> TokenStream { - // get the path of the scope argument + // Attempt to parse the scope path, returning on error if args.is_empty() { return input_and_compile_error( - args, syn::Error::new(proc_macro2::Span::call_site(), "invalid server definition, expected: #[scope(\"some path\")]")); + args.clone(), + syn::Error::new( + Span::call_site(), + "Missing arguments for scope macro, expected: #[scope(\"some path\")]", + ), + ); + } + let scope_path = syn::parse::(args.clone().into()); + if let Err(_err) = scope_path { + return input_and_compile_error( + args.clone(), + syn::Error::new( + Span::call_site(), + "Missing arguments for scope macro, expected: #[scope(\"some path\")]", + ), + ); } - let scope_path = syn::parse::(args.clone()).map_err(|_| syn::Error::new( - proc_macro2::Span::call_site(), - "Scope macro needs a path to be specified.", - )); - // modify the attribute of functions with method attributes in the module - let mut ast: syn::ItemMod = syn::parse(input.clone()).expect("expecting module for macro scope attribute"); - match scope_path { - Ok(scope_path) => { // scope_path : LitStr - if let Some((_, ref mut all_items)) = ast.content { - for item in all_items.iter_mut() { + // Expect macro to be for a module + match syn::parse::(input) { + Ok(mut ast) => { + // Modify the attributes of functions within the module with method with scope, if any + if let Some((_, ref mut items)) = ast.content { + items.iter_mut().for_each(|item| { if let Fn(fun) = item { - let mut new_attrs = Vec::new(); - for attr in fun.attrs.iter() { - if let Ok(_method) = MethodType::from_path(attr.path()) { - if let Ok(route_args) = attr.parse_args::() { - if let syn::Meta::List(meta_list) = attr.clone().meta { - let modified_path = format!("{}{}", scope_path.value(), route_args.path.value()); - let modified_tokens = quote! { - #modified_path - }; - let new_attr = syn::Attribute { - pound_token: attr.pound_token, - style: attr.style, - bracket_token: attr.bracket_token, - meta: syn::Meta::List(syn::MetaList { - path: meta_list.path, - delimiter: meta_list.delimiter, - tokens: modified_tokens, - }) - }; - new_attrs.push(new_attr); - } - else { - new_attrs.push(attr.clone()); - } - } - else { - new_attrs.push(attr.clone()); - } - } - else { - new_attrs.push(attr.clone()); - } - } - fun.attrs = new_attrs; + fun.attrs = fun + .attrs + .iter() + .map(|attr| { + modify_attribute_with_scope( + attr, + &scope_path.clone().unwrap().value(), + ) + }) + .collect(); } - } + }) } - }, - Err(err) => { - return input_and_compile_error(args, err); + TokenStream::from(quote! { #ast }) } - }; + Err(err) => { + input_and_compile_error(args, syn::Error::new(Span::call_site(), err.to_string())) + } + } +} - TokenStream::from(quote! { #ast }) -} \ No newline at end of file +// Check if the attribute is a method type and has a route path, then modify it +fn modify_attribute_with_scope(attr: &syn::Attribute, scope_path: &str) -> syn::Attribute { + match ( + MethodType::from_path(attr.path()), + attr.parse_args::(), + attr.clone().meta, + ) { + (Ok(_method), Ok(route_args), syn::Meta::List(meta_list)) => { + let modified_path = format!("{}{}", scope_path, route_args.path.value()); + let modified_tokens = quote! { #modified_path }; + syn::Attribute { + meta: syn::Meta::List(syn::MetaList { + tokens: modified_tokens, + ..meta_list.clone() + }), + ..attr.clone() + } + } + _ => attr.clone(), + } +} diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 54a0030ba..418812c1b 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -387,37 +387,24 @@ async fn test_wrap() { */ #[scope("/test")] mod scope_module { - use actix_web::{HttpResponse, Responder}; - use actix_web_codegen::{ - connect, delete, get, head, options, patch, post, put, route, routes, scope, trace, - }; - - #[get("/get_test")] - async fn get_test() -> impl Responder { - HttpResponse::Ok() - } -} -/* -#[scope("/test")] -const mod_inner: () = { use actix_web::{ - connect, delete, head, options, patch, put, trace, web, web::Json, HttpRequest, + get, post, connect, delete, head, options, patch, put, trace, web, HttpRequest, HttpResponse, Responder, }; - #[actix_web::get("/test")] + #[get("/test")] pub async fn test() -> impl Responder { - mod_test2() + HttpResponse::Ok().finish() } - #[actix_web::get("/twicetest/{value}")] + #[get("/twicetest/{value}")] pub async fn test_twice(value: web::Path) -> impl actix_web::Responder { let int_value: i32 = value.parse().unwrap_or(0); let doubled = int_value * 2; HttpResponse::Ok().body(format!("Twice value: {}", doubled)) } - #[actix_web::post("/test")] + #[post("/test")] pub async fn test_post() -> impl Responder { HttpResponse::Ok().body(format!("post works")) } @@ -464,31 +451,31 @@ const mod_inner: () = { pub fn mod_common(message: String) -> impl actix_web::Responder { HttpResponse::Ok().body(message) } -}; +} #[scope("/v1")] -const mod_inner_v1: () = { - use actix_web::Responder; +mod mod_inner_v1 { + use actix_web::{get, Responder}; - #[actix_web::get("/test")] + #[get("/test")] pub async fn test() -> impl Responder { - super::mod_inner_scope::mod_common("version1 works".to_string()) + super::scope_module::mod_common("version1 works".to_string()) } -}; +} #[scope("/v2")] -const mod_inner_v2: () = { - use actix_web::Responder; +mod mod_inner_v2 { + use actix_web::{get, Responder}; - #[actix_web::get("/test")] + #[get("/test")] pub async fn test() -> impl Responder { - super::mod_inner_scope::mod_common("version2 works".to_string()) + super::scope_module::mod_common("version2 works".to_string()) } -}; +} #[actix_rt::test] async fn test_scope_get_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test)); let request = srv.request(http::Method::GET, srv.url("/test/test")); let response = request.send().await.unwrap(); @@ -497,7 +484,7 @@ async fn test_scope_get_async() { #[actix_rt::test] async fn test_scope_get_param_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_twice)); let request = srv.request(http::Method::GET, srv.url("/test/twicetest/4")); let mut response = request.send().await.unwrap(); @@ -508,7 +495,7 @@ async fn test_scope_get_param_async() { #[actix_rt::test] async fn test_scope_post_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_post)); let request = srv.request(http::Method::POST, srv.url("/test/test")); let mut response = request.send().await.unwrap(); @@ -519,7 +506,7 @@ async fn test_scope_post_async() { #[actix_rt::test] async fn test_scope_put_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_put)); let request = srv.request(http::Method::PUT, srv.url("/test/test")); let mut response = request.send().await.unwrap(); @@ -530,7 +517,7 @@ async fn test_scope_put_async() { #[actix_rt::test] async fn test_scope_head_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_head)); let request = srv.request(http::Method::HEAD, srv.url("/test/test")); let response = request.send().await.unwrap(); @@ -539,7 +526,7 @@ async fn test_scope_head_async() { #[actix_rt::test] async fn test_scope_connect_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_connect)); let request = srv.request(http::Method::CONNECT, srv.url("/test/test")); let mut response = request.send().await.unwrap(); @@ -550,7 +537,7 @@ async fn test_scope_connect_async() { #[actix_rt::test] async fn test_scope_options_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_options)); let request = srv.request(http::Method::OPTIONS, srv.url("/test/test")); let mut response = request.send().await.unwrap(); @@ -561,7 +548,7 @@ async fn test_scope_options_async() { #[actix_rt::test] async fn test_scope_trace_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_trace)); let request = srv.request(http::Method::TRACE, srv.url("/test/test")); let mut response = request.send().await.unwrap(); @@ -572,7 +559,7 @@ async fn test_scope_trace_async() { #[actix_rt::test] async fn test_scope_patch_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_patch)); let request = srv.request(http::Method::PATCH, srv.url("/test/test")); let mut response = request.send().await.unwrap(); @@ -583,7 +570,7 @@ async fn test_scope_patch_async() { #[actix_rt::test] async fn test_scope_delete_async() { - let srv = actix_test::start(|| App::new().service(mod_inner)); + let srv = actix_test::start(|| App::new().service(scope_module::test_delete)); let request = srv.request(http::Method::DELETE, srv.url("/test/test")); let mut response = request.send().await.unwrap(); @@ -594,7 +581,7 @@ async fn test_scope_delete_async() { #[actix_rt::test] async fn test_scope_v1_v2_async() { - let srv = actix_test::start(|| App::new().service(mod_inner_v1).service(mod_inner_v2)); + let srv = actix_test::start(|| App::new().service(mod_inner_v1::test).service(mod_inner_v2::test)); let request = srv.request(http::Method::GET, srv.url("/v1/test")); let mut response = request.send().await.unwrap(); @@ -608,4 +595,3 @@ async fn test_scope_v1_v2_async() { let body_str = String::from_utf8(body.to_vec()).unwrap(); assert_eq!(body_str, "version2 works"); } -*/ \ No newline at end of file