mirror of https://github.com/fafhrd91/actix-web
work in progress. revised procedural macro to change othe macro call
This commit is contained in:
parent
f8f93d4dab
commit
c4520d909a
|
@ -254,10 +254,10 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
/// scope("/test")
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use actix_web_codegen::{scope};
|
/// use actix_web_codegen::{scope};
|
||||||
///
|
///
|
||||||
/// #[scope("/test")]
|
|
||||||
/// const mod_inner: () = {
|
/// const mod_inner: () = {
|
||||||
/// use actix_web::{HttpResponse, HttpRequest, Responder, put, head, connect, options, trace, patch, delete, web };
|
/// use actix_web::{HttpResponse, HttpRequest, Responder, put, head, connect, options, trace, patch, delete, web };
|
||||||
/// use actix_web::web::Json;
|
/// use actix_web::web::Json;
|
||||||
|
@ -320,12 +320,7 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
///};
|
///};
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// Internally the macro generates struct with name of scope (e.g. `mod_inner`).
|
|
||||||
/// And creates public module as `<name>_scope`.
|
|
||||||
///
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
route::ScopeArgs::new(args, input).generate()
|
route::with_scope(args, input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use std::{collections::HashSet, fmt};
|
use std::{collections::HashSet};
|
||||||
|
|
||||||
use actix_router::ResourceDef;
|
use actix_router::ResourceDef;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token, ItemFn};
|
||||||
|
use syn::Item::Fn; // todo: cleanup
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RouteArgs {
|
pub struct RouteArgs {
|
||||||
|
@ -331,14 +332,14 @@ pub struct Route {
|
||||||
args: Vec<Args>,
|
args: Vec<Args>,
|
||||||
|
|
||||||
/// AST of the handler function being annotated.
|
/// AST of the handler function being annotated.
|
||||||
ast: syn::ItemFn,
|
ast: ItemFn,
|
||||||
|
|
||||||
/// The doc comment attributes to copy to generated struct, if any.
|
/// The doc comment attributes to copy to generated struct, if any.
|
||||||
doc_attributes: Vec<syn::Attribute>,
|
doc_attributes: Vec<syn::Attribute>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Route {
|
impl Route {
|
||||||
pub fn new(args: RouteArgs, ast: syn::ItemFn, method: Option<MethodType>) -> syn::Result<Self> {
|
pub fn new(args: RouteArgs, ast: ItemFn, method: Option<MethodType>) -> syn::Result<Self> {
|
||||||
let name = ast.sig.ident.clone();
|
let name = ast.sig.ident.clone();
|
||||||
|
|
||||||
// Try and pull out the doc comments so that we can reapply them to the generated struct.
|
// Try and pull out the doc comments so that we can reapply them to the generated struct.
|
||||||
|
@ -374,7 +375,7 @@ impl Route {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn multiple(args: Vec<Args>, ast: syn::ItemFn) -> syn::Result<Self> {
|
fn multiple(args: Vec<Args>, ast: ItemFn) -> syn::Result<Self> {
|
||||||
let name = ast.sig.ident.clone();
|
let name = ast.sig.ident.clone();
|
||||||
|
|
||||||
// Try and pull out the doc comments so that we can reapply them to the generated struct.
|
// Try and pull out the doc comments so that we can reapply them to the generated struct.
|
||||||
|
@ -483,7 +484,7 @@ pub(crate) fn with_method(
|
||||||
Err(err) => return input_and_compile_error(input, err),
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ast = match syn::parse::<syn::ItemFn>(input.clone()) {
|
let ast = match syn::parse::<ItemFn>(input.clone()) {
|
||||||
Ok(ast) => ast,
|
Ok(ast) => ast,
|
||||||
// on parse error, make IDEs happy; see fn docs
|
// on parse error, make IDEs happy; see fn docs
|
||||||
Err(err) => return input_and_compile_error(input, err),
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
|
@ -497,7 +498,7 @@ pub(crate) fn with_method(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
||||||
let mut ast = match syn::parse::<syn::ItemFn>(input.clone()) {
|
let mut ast = match syn::parse::<ItemFn>(input.clone()) {
|
||||||
Ok(ast) => ast,
|
Ok(ast) => ast,
|
||||||
// on parse error, make IDEs happy; see fn docs
|
// on parse error, make IDEs happy; see fn docs
|
||||||
Err(err) => return input_and_compile_error(input, err),
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
|
@ -555,142 +556,72 @@ fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStrea
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements scope proc macro
|
// todo: test with enums in module...
|
||||||
///
|
// 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...
|
||||||
|
pub fn with_scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
// get the path of the scope argument
|
||||||
|
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\")]"));
|
||||||
|
}
|
||||||
|
let scope_path = syn::parse::<syn::LitStr>(args.clone()).map_err(|_| syn::Error::new(
|
||||||
|
proc_macro2::Span::call_site(),
|
||||||
|
"Scope macro needs a path to be specified.",
|
||||||
|
));
|
||||||
|
|
||||||
struct ScopeItems {
|
// modify the attribute of functions with method attributes in the module
|
||||||
handlers: Vec<String>,
|
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
|
||||||
impl ScopeItems {
|
if let Some((_, ref mut all_items)) = ast.content {
|
||||||
pub fn from_items(items: &[syn::Item]) -> Self {
|
for item in all_items.iter_mut() {
|
||||||
let mut handlers = Vec::new();
|
if let Fn(fun) = item {
|
||||||
|
let mut new_attrs = Vec::new();
|
||||||
for item in items {
|
for attr in fun.attrs.iter() {
|
||||||
match item {
|
if let Ok(_method) = MethodType::from_path(attr.path()) {
|
||||||
syn::Item::Fn(ref fun) => {
|
if let Ok(route_args) = attr.parse_args::<RouteArgs>() {
|
||||||
for attr in fun.attrs.iter() {
|
if let syn::Meta::List(meta_list) = attr.clone().meta {
|
||||||
for bound in attr.path().segments.iter() {
|
let modified_path = format!("{}{}", scope_path.value(), route_args.path.value());
|
||||||
if bound.ident == "get"
|
let modified_tokens = quote! {
|
||||||
|| bound.ident == "post"
|
#modified_path
|
||||||
|| bound.ident == "put"
|
};
|
||||||
|| bound.ident == "head"
|
let new_attr = syn::Attribute {
|
||||||
|| bound.ident == "connect"
|
pound_token: attr.pound_token,
|
||||||
|| bound.ident == "options"
|
style: attr.style,
|
||||||
|| bound.ident == "trace"
|
bracket_token: attr.bracket_token,
|
||||||
|| bound.ident == "patch"
|
meta: syn::Meta::List(syn::MetaList {
|
||||||
|| bound.ident == "delete"
|
path: meta_list.path,
|
||||||
{
|
delimiter: meta_list.delimiter,
|
||||||
handlers.push(format!("{}", fun.sig.ident));
|
tokens: modified_tokens,
|
||||||
break;
|
})
|
||||||
|
};
|
||||||
|
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;
|
||||||
}
|
|
||||||
_ => 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"),
|
},
|
||||||
|
Err(err) => {
|
||||||
|
return input_and_compile_error(args, err);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let scope_items = ScopeItems::from_items(&items);
|
TokenStream::from(quote! { #ast })
|
||||||
|
}
|
||||||
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}}")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -371,7 +371,7 @@ async fn test_auto_async() {
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn test_wrap() {
|
async fn test_wrap() {
|
||||||
let srv = actix_test::start(|| App::new().service(get_wrap));
|
let srv = actix_test::start(|| App::new().service(get_wrap));
|
||||||
|
@ -384,7 +384,20 @@ async fn test_wrap() {
|
||||||
let body = String::from_utf8(body.to_vec()).unwrap();
|
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||||
assert!(body.contains("wrong number of parameters"));
|
assert!(body.contains("wrong number of parameters"));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
#[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")]
|
#[scope("/test")]
|
||||||
const mod_inner: () = {
|
const mod_inner: () = {
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
|
@ -595,3 +608,4 @@ async fn test_scope_v1_v2_async() {
|
||||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||||
assert_eq!(body_str, "version2 works");
|
assert_eq!(body_str, "version2 works");
|
||||||
}
|
}
|
||||||
|
*/
|
Loading…
Reference in New Issue