mirror of https://github.com/fafhrd91/actix-web
WIP: basic implementation for `routes` macro
This commit is contained in:
parent
6a5b370206
commit
b962959b82
|
@ -104,6 +104,11 @@ pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
route::with_method(None, args, input)
|
route::with_method(None, args, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn routes(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
route::with_methods(input)
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! method_macro {
|
macro_rules! method_macro {
|
||||||
($variant:ident, $method:ident) => {
|
($variant:ident, $method:ident) => {
|
||||||
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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::{format_ident, quote, ToTokens, TokenStreamExt};
|
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta};
|
use syn::{parse_macro_input, Attribute, AttributeArgs, Ident, LitStr, Meta, NestedMeta, Path};
|
||||||
|
|
||||||
enum ResourceType {
|
enum ResourceType {
|
||||||
Async,
|
Async,
|
||||||
|
@ -20,7 +20,7 @@ impl ToTokens for ResourceType {
|
||||||
|
|
||||||
macro_rules! method_type {
|
macro_rules! method_type {
|
||||||
(
|
(
|
||||||
$($variant:ident, $upper:ident,)+
|
$($variant:ident, $upper:ident, $lower:ident,)+
|
||||||
) => {
|
) => {
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub enum MethodType {
|
pub enum MethodType {
|
||||||
|
@ -42,20 +42,27 @@ macro_rules! method_type {
|
||||||
_ => Err(format!("Unexpected HTTP method: `{}`", method)),
|
_ => Err(format!("Unexpected HTTP method: `{}`", method)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_path(method: &Path) -> Result<Self, ()> {
|
||||||
|
match () {
|
||||||
|
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
method_type! {
|
method_type! {
|
||||||
Get, GET,
|
Get, GET, get,
|
||||||
Post, POST,
|
Post, POST, post,
|
||||||
Put, PUT,
|
Put, PUT, put,
|
||||||
Delete, DELETE,
|
Delete, DELETE, delete,
|
||||||
Head, HEAD,
|
Head, HEAD, head,
|
||||||
Connect, CONNECT,
|
Connect, CONNECT, connect,
|
||||||
Options, OPTIONS,
|
Options, OPTIONS, options,
|
||||||
Trace, TRACE,
|
Trace, TRACE, trace,
|
||||||
Patch, PATCH,
|
Patch, PATCH, patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for MethodType {
|
impl ToTokens for MethodType {
|
||||||
|
@ -90,6 +97,18 @@ impl Args {
|
||||||
let mut wrappers = Vec::new();
|
let mut wrappers = Vec::new();
|
||||||
let mut methods = HashSet::new();
|
let mut methods = HashSet::new();
|
||||||
|
|
||||||
|
if args.is_empty() {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
format!(
|
||||||
|
r#"invalid service definition, expected #[{}("<some path>")]"#,
|
||||||
|
method
|
||||||
|
.map_or("route", |it| it.as_str())
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let is_route_macro = method.is_none();
|
let is_route_macro = method.is_none();
|
||||||
if let Some(method) = method {
|
if let Some(method) = method {
|
||||||
methods.insert(method);
|
methods.insert(method);
|
||||||
|
@ -184,7 +203,7 @@ impl Args {
|
||||||
|
|
||||||
pub struct Route {
|
pub struct Route {
|
||||||
name: syn::Ident,
|
name: syn::Ident,
|
||||||
args: Args,
|
args: Vec<Args>,
|
||||||
ast: syn::ItemFn,
|
ast: syn::ItemFn,
|
||||||
resource_type: ResourceType,
|
resource_type: ResourceType,
|
||||||
|
|
||||||
|
@ -220,18 +239,6 @@ impl Route {
|
||||||
ast: syn::ItemFn,
|
ast: syn::ItemFn,
|
||||||
method: Option<MethodType>,
|
method: Option<MethodType>,
|
||||||
) -> syn::Result<Self> {
|
) -> syn::Result<Self> {
|
||||||
if args.is_empty() {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
Span::call_site(),
|
|
||||||
format!(
|
|
||||||
r#"invalid service definition, expected #[{}("<some path>")]"#,
|
|
||||||
method
|
|
||||||
.map_or("route", |it| it.as_str())
|
|
||||||
.to_ascii_lowercase()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
||||||
|
@ -265,6 +272,41 @@ impl Route {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
args: vec![args],
|
||||||
|
ast,
|
||||||
|
resource_type,
|
||||||
|
doc_attributes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multiple(args: Vec<Args>, ast: syn::ItemFn) -> syn::Result<Self> {
|
||||||
|
let name = ast.sig.ident.clone();
|
||||||
|
|
||||||
|
// Try and pull out the doc comments so that we can reapply them to the generated struct.
|
||||||
|
// Note that multi line doc comments are converted to multiple doc attributes.
|
||||||
|
let doc_attributes = ast
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| attr.path.is_ident("doc"))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let resource_type = if ast.sig.asyncness.is_some() {
|
||||||
|
ResourceType::Async
|
||||||
|
} else {
|
||||||
|
match ast.sig.output {
|
||||||
|
syn::ReturnType::Default => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
ast,
|
||||||
|
"Function has no return type. Cannot be used as handler",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
args,
|
args,
|
||||||
|
@ -280,17 +322,21 @@ impl ToTokens for Route {
|
||||||
let Self {
|
let Self {
|
||||||
name,
|
name,
|
||||||
ast,
|
ast,
|
||||||
args:
|
args,
|
||||||
Args {
|
resource_type,
|
||||||
|
doc_attributes,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let registrations: TokenStream2 = args
|
||||||
|
.iter()
|
||||||
|
.map(
|
||||||
|
|Args {
|
||||||
path,
|
path,
|
||||||
resource_name,
|
resource_name,
|
||||||
guards,
|
guards,
|
||||||
wrappers,
|
wrappers,
|
||||||
methods,
|
methods,
|
||||||
},
|
}| {
|
||||||
resource_type,
|
|
||||||
doc_attributes,
|
|
||||||
} = self;
|
|
||||||
let resource_name = resource_name
|
let resource_name = resource_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or_else(|| name.to_string(), LitStr::value);
|
.map_or_else(|| name.to_string(), LitStr::value);
|
||||||
|
@ -312,6 +358,19 @@ impl ToTokens for Route {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
quote! {
|
||||||
|
let __resource = ::actix_web::Resource::new(#path)
|
||||||
|
.name(#resource_name)
|
||||||
|
#method_guards
|
||||||
|
#(.guard(::actix_web::guard::fn_guard(#guards)))*
|
||||||
|
#(.wrap(#wrappers))*
|
||||||
|
.#resource_type(#name);
|
||||||
|
|
||||||
|
::actix_web::dev::HttpServiceFactory::register(__resource, __config);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
let stream = quote! {
|
let stream = quote! {
|
||||||
#(#doc_attributes)*
|
#(#doc_attributes)*
|
||||||
|
@ -321,14 +380,7 @@ impl ToTokens for Route {
|
||||||
impl ::actix_web::dev::HttpServiceFactory for #name {
|
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||||
#ast
|
#ast
|
||||||
let __resource = ::actix_web::Resource::new(#path)
|
#registrations
|
||||||
.name(#resource_name)
|
|
||||||
#method_guards
|
|
||||||
#(.guard(::actix_web::guard::fn_guard(#guards)))*
|
|
||||||
#(.wrap(#wrappers))*
|
|
||||||
.#resource_type(#name);
|
|
||||||
|
|
||||||
::actix_web::dev::HttpServiceFactory::register(__resource, __config)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -357,6 +409,49 @@ pub(crate) fn with_method(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
||||||
|
let mut ast = match syn::parse::<syn::ItemFn>(input.clone()) {
|
||||||
|
Ok(ast) => ast,
|
||||||
|
// on parse error, make IDEs happy; see fn docs
|
||||||
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (methods, others): (Vec<Result<(MethodType, Attribute), Attribute>>, _) = ast
|
||||||
|
.attrs
|
||||||
|
.into_iter()
|
||||||
|
.map(|attr| match MethodType::from_path(&attr.path) {
|
||||||
|
Ok(method) => Ok((method, attr)),
|
||||||
|
Err(_) => Err(attr),
|
||||||
|
})
|
||||||
|
.partition(Result::is_ok);
|
||||||
|
|
||||||
|
ast.attrs = others.into_iter().map(Result::unwrap_err).collect();
|
||||||
|
|
||||||
|
let methods = match methods
|
||||||
|
.into_iter()
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.map(|(method, attr)| {
|
||||||
|
attr.parse_meta().and_then(|args| {
|
||||||
|
if let Meta::List(args) = args {
|
||||||
|
Args::new(args.nested.into_iter().collect(), Some(method))
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new_spanned(attr, "Invalid input for macro"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
{
|
||||||
|
Ok(methods) => methods,
|
||||||
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
|
};
|
||||||
|
|
||||||
|
match Route::multiple(methods, ast) {
|
||||||
|
Ok(route) => route.into_token_stream().into(),
|
||||||
|
// on macro related error, make IDEs happy; see fn docs
|
||||||
|
Err(err) => input_and_compile_error(input, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts the error to a token stream and appends it to the original input.
|
/// Converts the error to a token stream and appends it to the original input.
|
||||||
///
|
///
|
||||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||||
|
|
Loading…
Reference in New Issue