mirror of
https://github.com/fafhrd91/actix-web
synced 2025-06-26 06:57:43 +02:00
Scope macro (#3136)
* 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>
This commit is contained in:
200
actix-web-codegen/tests/scopes.rs
Normal file
200
actix-web-codegen/tests/scopes.rs
Normal file
@ -0,0 +1,200 @@
|
||||
use actix_web::{guard::GuardContext, http, http::header, web, App, HttpResponse, Responder};
|
||||
use actix_web_codegen::{delete, get, post, route, routes, scope};
|
||||
|
||||
pub fn image_guard(ctx: &GuardContext) -> bool {
|
||||
ctx.header::<header::Accept>()
|
||||
.map(|h| h.preference() == "image/*")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[scope("/test")]
|
||||
mod scope_module {
|
||||
// ensure that imports can be brought into the scope
|
||||
use super::*;
|
||||
|
||||
#[get("/test/guard", guard = "image_guard")]
|
||||
pub async fn guard() -> impl Responder {
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
#[get("/test")]
|
||||
pub async fn test() -> impl Responder {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
#[get("/twice-test/{value}")]
|
||||
pub async fn twice(value: web::Path<String>) -> 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))
|
||||
}
|
||||
|
||||
#[post("/test")]
|
||||
pub async fn post() -> impl Responder {
|
||||
HttpResponse::Ok().body("post works")
|
||||
}
|
||||
|
||||
#[delete("/test")]
|
||||
pub async fn delete() -> impl Responder {
|
||||
"delete works"
|
||||
}
|
||||
|
||||
#[route("/test", method = "PUT", method = "PATCH", method = "CUSTOM")]
|
||||
pub async fn multiple_shared_path() -> impl Responder {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
#[routes]
|
||||
#[head("/test1")]
|
||||
#[connect("/test2")]
|
||||
#[options("/test3")]
|
||||
#[trace("/test4")]
|
||||
async fn multiple_separate_paths() -> impl Responder {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
// test calling this from other mod scope with scope attribute...
|
||||
pub fn mod_common(message: String) -> impl actix_web::Responder {
|
||||
HttpResponse::Ok().body(message)
|
||||
}
|
||||
}
|
||||
|
||||
/// Scope doc string to check in cargo expand.
|
||||
#[scope("/v1")]
|
||||
mod mod_scope_v1 {
|
||||
use super::*;
|
||||
|
||||
/// Route doc string to check in cargo expand.
|
||||
#[get("/test")]
|
||||
pub async fn test() -> impl Responder {
|
||||
scope_module::mod_common("version1 works".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[scope("/v2")]
|
||||
mod mod_scope_v2 {
|
||||
use super::*;
|
||||
|
||||
// check to make sure non-function tokens in the scope block are preserved...
|
||||
enum TestEnum {
|
||||
Works,
|
||||
}
|
||||
|
||||
#[get("/test")]
|
||||
pub async fn test() -> impl Responder {
|
||||
// make sure this type still exists...
|
||||
let test_enum = TestEnum::Works;
|
||||
|
||||
match test_enum {
|
||||
TestEnum::Works => scope_module::mod_common("version2 works".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_get_async() {
|
||||
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();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_get_param_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::twice));
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/test/twice-test/4"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "Twice value: 8");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_post_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::post));
|
||||
|
||||
let request = srv.request(http::Method::POST, srv.url("/test/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "post works");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn multiple_shared_path_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::multiple_shared_path));
|
||||
|
||||
let request = srv.request(http::Method::PUT, srv.url("/test/test"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::PATCH, srv.url("/test/test"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn multiple_multi_path_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::multiple_separate_paths));
|
||||
|
||||
let request = srv.request(http::Method::HEAD, srv.url("/test/test1"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::CONNECT, srv.url("/test/test2"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::OPTIONS, srv.url("/test/test3"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::TRACE, srv.url("/test/test4"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_delete_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::delete));
|
||||
|
||||
let request = srv.request(http::Method::DELETE, srv.url("/test/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "delete works");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_get_with_guard_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::guard));
|
||||
|
||||
let request = srv
|
||||
.request(http::Method::GET, srv.url("/test/test/guard"))
|
||||
.insert_header(("Accept", "image/*"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_v1_v2_async() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new()
|
||||
.service(mod_scope_v1::test)
|
||||
.service(mod_scope_v2::test)
|
||||
});
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/v1/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "version1 works");
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/v2/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "version2 works");
|
||||
}
|
@ -18,6 +18,11 @@ fn compile_macros() {
|
||||
t.compile_fail("tests/trybuild/routes-missing-method-fail.rs");
|
||||
t.compile_fail("tests/trybuild/routes-missing-args-fail.rs");
|
||||
|
||||
t.compile_fail("tests/trybuild/scope-on-handler.rs");
|
||||
t.compile_fail("tests/trybuild/scope-missing-args.rs");
|
||||
t.compile_fail("tests/trybuild/scope-invalid-args.rs");
|
||||
t.compile_fail("tests/trybuild/scope-trailing-slash.rs");
|
||||
|
||||
t.pass("tests/trybuild/docstring-ok.rs");
|
||||
|
||||
t.pass("tests/trybuild/test-runtime.rs");
|
||||
|
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
const PATH: &str = "/api";
|
||||
|
||||
#[scope(PATH)]
|
||||
mod api_const {}
|
||||
|
||||
#[scope(true)]
|
||||
mod api_bool {}
|
||||
|
||||
#[scope(123)]
|
||||
mod api_num {}
|
||||
|
||||
fn main() {}
|
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
@ -0,0 +1,17 @@
|
||||
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-invalid-args.rs:5:9
|
||||
|
|
||||
5 | #[scope(PATH)]
|
||||
| ^^^^
|
||||
|
||||
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-invalid-args.rs:8:9
|
||||
|
|
||||
8 | #[scope(true)]
|
||||
| ^^^^
|
||||
|
||||
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-invalid-args.rs:11:9
|
||||
|
|
||||
11 | #[scope(123)]
|
||||
| ^^^
|
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
#[scope]
|
||||
mod api {}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,7 @@
|
||||
error: missing arguments for scope macro, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-missing-args.rs:3:1
|
||||
|
|
||||
3 | #[scope]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `scope` (in Nightly builds, run with -Z macro-backtrace for more info)
|
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
#[scope("/api")]
|
||||
async fn index() -> &'static str {
|
||||
"Hello World!"
|
||||
}
|
||||
|
||||
fn main() {}
|
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: #[scope] macro must be attached to a module
|
||||
--> tests/trybuild/scope-on-handler.rs:4:1
|
||||
|
|
||||
4 | async fn index() -> &'static str {
|
||||
| ^^^^^
|
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
#[scope("/api/")]
|
||||
mod api {}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,5 @@
|
||||
error: scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes
|
||||
--> tests/trybuild/scope-trailing-slash.rs:3:9
|
||||
|
|
||||
3 | #[scope("/api/")]
|
||||
| ^^^^^^^
|
Reference in New Issue
Block a user