Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope macro #3136

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ad90bc9
add scope proc macro
pmd3d Sep 3, 2023
ac82b56
Update scope macro code to work with current HttpServiceFactory
pmd3d Sep 10, 2023
db69279
started some test code
pmd3d Sep 10, 2023
6bedb95
add some unit tests
pmd3d Sep 14, 2023
efe990d
Merge branch 'master' into scope_work
pmd3d Sep 14, 2023
ec4633a
code formatting cleanup
pmd3d Sep 14, 2023
3a57148
Merge branch 'master' into scope_work
pmd3d Sep 16, 2023
3e4c643
add another test for combining and calling 2 scopes
pmd3d Sep 16, 2023
4ae7a00
Merge branch 'master' into scope_work
pmd3d Sep 18, 2023
3c3b5d0
Merge branch 'master' into scope_work
pmd3d Sep 21, 2023
e931a58
format code with formatter
pmd3d Sep 21, 2023
40b8ec7
Merge branch 'master' into scope_work
pmd3d Oct 9, 2023
03501cb
Merge branch 'master' into scope_work
pmd3d Oct 12, 2023
bbf5cf3
Merge branch 'master' into scope_work
pmd3d Oct 15, 2023
4f11358
Merge branch 'master' into scope_work
pmd3d Oct 24, 2023
e3432c8
Merge branch 'master' into scope_work
pmd3d Nov 3, 2023
22e51a4
Merge branch 'master' into scope_work
pmd3d Nov 20, 2023
b05197a
Merge branch 'master' into scope_work
pmd3d Nov 20, 2023
5d291e1
Merge branch 'master' into scope_work
pmd3d Nov 20, 2023
567deaa
Merge branch 'master' into scope_work
pmd3d Nov 28, 2023
f9bd347
Merge branch 'master' into scope_work
pmd3d Dec 7, 2023
3ee0dc9
Update actix-web-codegen/src/lib.rs with comment documentation fix
pmd3d Dec 10, 2023
d6ee277
Merge branch 'master' into scope_work
pmd3d Dec 16, 2023
f82e740
Merge branch 'master' into scope_work
pmd3d Dec 22, 2023
88883b7
Merge branch 'master' into scope_work
pmd3d Dec 29, 2023
f01b4cd
Merge branch 'master' into scope_work
pmd3d Jan 6, 2024
687667f
Merge branch 'master' into scope_work
pmd3d Jan 9, 2024
4a8b05c
Merge branch 'master' into scope_work
pmd3d Jan 11, 2024
16c84c2
Merge branch 'master' into scope_work
pmd3d Jan 25, 2024
f8f93d4
Merge branch 'master' into scope_work
pmd3d Feb 18, 2024
c4520d9
work in progress. revised procedural macro to change othe macro call
pmd3d Apr 1, 2024
455c064
Merge branch 'master' into scope_work
pmd3d Apr 1, 2024
ab73c72
add tests again. refactor nested code.
pmd3d Apr 1, 2024
828be28
clean up code. fix bugs with route and method attributes with parameters
pmd3d Apr 3, 2024
4fb51ad
clean up for rust fmt
pmd3d Apr 3, 2024
bcc0bed
clean up for rust fmt
pmd3d Apr 3, 2024
2f4b859
fix out of date comment for scope macro
pmd3d Apr 3, 2024
6eabb23
Merge branch 'master' into scope_work
pmd3d Apr 9, 2024
6d3fff2
Merge branch 'master' into scope_work
pmd3d Apr 17, 2024
89f7819
sync to master branch by adding test_wrap
pmd3d Apr 17, 2024
d35801c
needed to format code
pmd3d Apr 17, 2024
3137987
Merge branch 'master' into scope_work
pmd3d May 5, 2024
4606644
Merge branch 'master' into scope_work
pmd3d May 10, 2024
7b93780
Merge branch 'master' into scope_work
robjtede May 14, 2024
a3d6a49
Merge branch 'master' into scope_work
robjtede May 14, 2024
c3af757
Merge branch 'master' into scope_work
pmd3d May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions actix-web-codegen/CHANGES.md
Expand Up @@ -2,6 +2,10 @@

## Unreleased

## 4.2.3

- Add a scope macro that takes a path

## 4.2.2

- Fix regression when declaring `wrap` attribute using an expression.
Expand Down
89 changes: 89 additions & 0 deletions actix-web-codegen/src/lib.rs
Expand Up @@ -240,3 +240,92 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
output.extend(item);
output
}

/// Generates scope
///
/// Syntax: `#[scope("path")]`
///
/// Due to current limitation it cannot be applied to modules themself.
/// Instead one should create const variable that contains module code.
///
/// ## Attributes:
///
/// - `"path"` - Raw literal string with path for which to register handler. Mandatory.
///
/// # Example
///
/// ```rust
/// use actix_web_codegen::{scope};
///
/// #[scope("/test")]
/// const mod_inner: () = {
/// use actix_web::{HttpResponse, HttpRequest, Responder, put, head, connect, options, trace, patch, delete, web };
/// use actix_web::web::Json;
///
/// #[actix_web::get("/test")]
/// pub async fn test() -> impl Responder {
/// mod_test2()
/// }
///
/// #[actix_web::get("/twicetest/{value}")]
/// pub async fn test_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))
/// }
///
/// #[actix_web::post("/test")]
/// pub async fn test_post() -> impl Responder {
/// HttpResponse::Ok().body(format!("post works"))
/// }
///
/// #[put("/test")]
/// pub async fn test_put() -> impl Responder {
/// HttpResponse::Ok().body(format!("put works"))
/// }
///
/// #[head("/test")]
/// pub async fn test_head() -> impl Responder {
/// HttpResponse::Ok().finish()
/// }
///
/// #[connect("/test")]
/// pub async fn test_connect() -> impl Responder {
/// HttpResponse::Ok().body("CONNECT works")
/// }
///
/// #[options("/test")]
/// pub async fn test_options() -> impl Responder {
/// HttpResponse::Ok().body("OPTIONS works")
/// }
///
/// #[trace("/test")]
/// pub async fn test_trace(req: HttpRequest) -> impl Responder {
/// HttpResponse::Ok().body(format!("TRACE works: {:?}", req))
/// }
///
/// #[patch("/test")]
/// pub async fn test_patch() -> impl Responder {
/// HttpResponse::Ok().body(format!("patch works"))
/// }
///
/// #[delete("/test")]
/// pub async fn test_delete() -> impl Responder {
/// HttpResponse::Ok().body("DELETE works")
/// }
///
/// pub fn mod_test2() -> impl actix_web::Responder {
/// actix_web::HttpResponse::Ok().finish()
/// }
///};
/// ```
///
/// # Note
///
/// Internally the macro generate struct with name of scope (e.g. `mod_inner`)
/// And create public module as `<name>_scope`
pmd3d marked this conversation as resolved.
Show resolved Hide resolved
///
#[proc_macro_attribute]
pub fn scope(args: TokenStream, input: TokenStream) -> TokenStream {
route::ScopeArgs::new(args, input).generate()
}
142 changes: 141 additions & 1 deletion actix-web-codegen/src/route.rs
@@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::{collections::HashSet, fmt};

use actix_router::ResourceDef;
use proc_macro::TokenStream;
Expand Down Expand Up @@ -554,3 +554,143 @@ fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStrea
item.extend(compile_err);
item
}

/// Implements scope proc macro
///

struct ScopeItems {
handlers: Vec<String>,
}

impl ScopeItems {
pub fn from_items(items: &[syn::Item]) -> Self {
let mut handlers = Vec::new();

for item in items {
match item {
syn::Item::Fn(ref fun) => {
for attr in fun.attrs.iter() {
for bound in attr.path().segments.iter() {
if bound.ident == "get"
|| bound.ident == "post"
|| bound.ident == "put"
|| bound.ident == "head"
|| bound.ident == "connect"
|| bound.ident == "options"
|| bound.ident == "trace"
|| bound.ident == "patch"
|| bound.ident == "delete"
{
handlers.push(format!("{}", fun.sig.ident));
break;
}
}
}
}
_ => continue,
}
}

Self { handlers }
}
}

pub struct ScopeArgs {
ast: syn::ItemConst,
name: syn::Ident,
path: String,
scope_items: ScopeItems,
}

impl ScopeArgs {
pub fn new(args: TokenStream, input: TokenStream) -> Self {
if args.is_empty() {
panic!("invalid server definition, expected: #[scope(\"some path\")]");
}

let ast: syn::ItemConst = syn::parse(input).expect("Parse input as module");
//TODO: we should change it to mod once supported on stable
//let ast: syn::ItemMod = syn::parse(input).expect("Parse input as module");
let name = ast.ident.clone();

let mut items = Vec::new();
match ast.expr.as_ref() {
syn::Expr::Block(expr) => {
for item in expr.block.stmts.iter() {
match item {
syn::Stmt::Item(ref item) => items.push(item.clone()),
_ => continue,
}
}
}
_ => panic!("Scope should containt only code block"),
}

let scope_items = ScopeItems::from_items(&items);

let mut path = None;
if let Ok(parsed) = syn::parse::<syn::LitStr>(args) {
path = Some(parsed.value());
}

let path = path.expect("Scope's path is not specified!");

Self {
ast,
name,
path,
scope_items,
}
}

pub fn generate(&self) -> TokenStream {
let text = self.to_string();

match text.parse() {
Ok(res) => res,
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text),
}
}
}

impl fmt::Display for ScopeArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ast = &self.ast;

let module_name = format!("{}_scope", self.name);
let module_name = syn::Ident::new(&module_name, ast.ident.span());
let ast = match ast.expr.as_ref() {
syn::Expr::Block(expr) => quote!(pub mod #module_name #expr),
_ => panic!("Unexpect non-block ast in scope macro"),
};

writeln!(f, "{}\n", ast)?;
writeln!(f, "#[allow(non_camel_case_types)]")?;
writeln!(f, "struct {};\n", self.name)?;
writeln!(
f,
"impl actix_web::dev::HttpServiceFactory for {} {{",
self.name
)?;
writeln!(
f,
" fn register(self, __config: &mut actix_web::dev::AppService) {{"
)?;
write!(
f,
" let scope = actix_web::Scope::new(\"{}\")",
self.path
)?;

for handler in self.scope_items.handlers.iter() {
write!(f, ".service({}::{})", module_name, handler)?;
}

writeln!(f, ";\n")?;
writeln!(
f,
" actix_web::dev::HttpServiceFactory::register(scope, __config)"
)?;
writeln!(f, " }}\n}}")
}
}