2023-07-02 02:09:15 +02:00
|
|
|
use std::collections::HashSet;
|
2020-09-16 23:37:41 +02:00
|
|
|
|
2021-08-30 22:50:40 +02:00
|
|
|
use actix_router::ResourceDef;
|
2019-03-31 09:23:15 +02:00
|
|
|
use proc_macro::TokenStream;
|
2019-10-14 17:34:17 +02:00
|
|
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
2022-07-04 06:31:49 +02:00
|
|
|
use quote::{quote, ToTokens, TokenStreamExt};
|
|
|
|
use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, Meta, NestedMeta, Path};
|
2019-03-31 09:23:15 +02:00
|
|
|
|
2023-02-06 18:06:47 +01:00
|
|
|
macro_rules! standard_method_type {
|
2020-09-22 23:42:51 +02:00
|
|
|
(
|
2022-07-04 06:31:49 +02:00
|
|
|
$($variant:ident, $upper:ident, $lower:ident,)+
|
2020-09-22 23:42:51 +02:00
|
|
|
) => {
|
2023-02-06 18:06:47 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
2020-09-22 23:42:51 +02:00
|
|
|
pub enum MethodType {
|
|
|
|
$(
|
|
|
|
$variant,
|
|
|
|
)+
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MethodType {
|
|
|
|
fn as_str(&self) -> &'static str {
|
|
|
|
match self {
|
|
|
|
$(Self::$variant => stringify!($variant),)+
|
|
|
|
}
|
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
|
2020-09-22 23:42:51 +02:00
|
|
|
fn parse(method: &str) -> Result<Self, String> {
|
|
|
|
match method {
|
|
|
|
$(stringify!($upper) => Ok(Self::$variant),)+
|
2023-02-06 18:06:47 +01:00
|
|
|
_ => Err(format!("HTTP method must be uppercase: `{}`", method)),
|
2020-09-22 23:42:51 +02:00
|
|
|
}
|
|
|
|
}
|
2022-07-04 06:31:49 +02:00
|
|
|
|
|
|
|
fn from_path(method: &Path) -> Result<Self, ()> {
|
|
|
|
match () {
|
|
|
|
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
|
|
|
_ => Err(()),
|
|
|
|
}
|
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
2020-09-22 23:42:51 +02:00
|
|
|
};
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
|
2023-02-06 18:06:47 +01:00
|
|
|
standard_method_type! {
|
2022-07-04 06:31:49 +02:00
|
|
|
Get, GET, get,
|
|
|
|
Post, POST, post,
|
|
|
|
Put, PUT, put,
|
|
|
|
Delete, DELETE, delete,
|
|
|
|
Head, HEAD, head,
|
|
|
|
Connect, CONNECT, connect,
|
|
|
|
Options, OPTIONS, options,
|
|
|
|
Trace, TRACE, trace,
|
|
|
|
Patch, PATCH, patch,
|
2023-02-06 18:06:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TryFrom<&syn::LitStr> for MethodType {
|
|
|
|
type Error = syn::Error;
|
|
|
|
|
|
|
|
fn try_from(value: &syn::LitStr) -> Result<Self, Self::Error> {
|
|
|
|
Self::parse(value.value().as_str())
|
|
|
|
.map_err(|message| syn::Error::new_spanned(value, message))
|
|
|
|
}
|
2020-09-22 23:42:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ToTokens for MethodType {
|
2019-10-14 17:34:17 +02:00
|
|
|
fn to_tokens(&self, stream: &mut TokenStream2) {
|
2020-03-14 23:23:28 +01:00
|
|
|
let ident = Ident::new(self.as_str(), Span::call_site());
|
2019-10-14 17:34:17 +02:00
|
|
|
stream.append(ident);
|
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
|
2023-02-06 18:06:47 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
enum MethodTypeExt {
|
|
|
|
Standard(MethodType),
|
|
|
|
Custom(LitStr),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MethodTypeExt {
|
|
|
|
/// Returns a single method guard token stream.
|
|
|
|
fn to_tokens_single_guard(&self) -> TokenStream2 {
|
|
|
|
match self {
|
|
|
|
MethodTypeExt::Standard(method) => {
|
|
|
|
quote! {
|
|
|
|
.guard(::actix_web::guard::#method())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MethodTypeExt::Custom(lit) => {
|
|
|
|
quote! {
|
|
|
|
.guard(::actix_web::guard::Method(
|
|
|
|
::actix_web::http::Method::from_bytes(#lit.as_bytes()).unwrap()
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a multi-method guard chain token stream.
|
|
|
|
fn to_tokens_multi_guard(&self, or_chain: Vec<impl ToTokens>) -> TokenStream2 {
|
|
|
|
debug_assert!(
|
2023-02-13 22:09:20 +01:00
|
|
|
!or_chain.is_empty(),
|
2023-02-06 18:06:47 +01:00
|
|
|
"empty or_chain passed to multi-guard constructor"
|
|
|
|
);
|
|
|
|
|
|
|
|
match self {
|
|
|
|
MethodTypeExt::Standard(method) => {
|
|
|
|
quote! {
|
|
|
|
.guard(
|
|
|
|
::actix_web::guard::Any(::actix_web::guard::#method())
|
|
|
|
#(#or_chain)*
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MethodTypeExt::Custom(lit) => {
|
|
|
|
quote! {
|
|
|
|
.guard(
|
|
|
|
::actix_web::guard::Any(
|
|
|
|
::actix_web::guard::Method(
|
|
|
|
::actix_web::http::Method::from_bytes(#lit.as_bytes()).unwrap()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
#(#or_chain)*
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a token stream containing the `.or` chain to be passed in to
|
|
|
|
/// [`MethodTypeExt::to_tokens_multi_guard()`].
|
|
|
|
fn to_tokens_multi_guard_or_chain(&self) -> TokenStream2 {
|
|
|
|
match self {
|
|
|
|
MethodTypeExt::Standard(method_type) => {
|
|
|
|
quote! {
|
|
|
|
.or(::actix_web::guard::#method_type())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MethodTypeExt::Custom(lit) => {
|
|
|
|
quote! {
|
|
|
|
.or(
|
|
|
|
::actix_web::guard::Method(
|
|
|
|
::actix_web::http::Method::from_bytes(#lit.as_bytes()).unwrap()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-06 13:40:41 +01:00
|
|
|
impl ToTokens for MethodTypeExt {
|
|
|
|
fn to_tokens(&self, stream: &mut TokenStream2) {
|
2023-02-06 18:06:47 +01:00
|
|
|
match self {
|
|
|
|
MethodTypeExt::Custom(lit_str) => {
|
|
|
|
let ident = Ident::new(lit_str.value().as_str(), Span::call_site());
|
2023-02-06 13:40:41 +01:00
|
|
|
stream.append(ident);
|
|
|
|
}
|
2023-02-06 18:06:47 +01:00
|
|
|
MethodTypeExt::Standard(method) => method.to_tokens(stream),
|
2023-02-06 13:40:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-06 18:06:47 +01:00
|
|
|
impl TryFrom<&syn::LitStr> for MethodTypeExt {
|
2020-09-16 23:37:41 +02:00
|
|
|
type Error = syn::Error;
|
|
|
|
|
|
|
|
fn try_from(value: &syn::LitStr) -> Result<Self, Self::Error> {
|
2023-02-06 18:06:47 +01:00
|
|
|
match MethodType::try_from(value) {
|
|
|
|
Ok(method) => Ok(MethodTypeExt::Standard(method)),
|
|
|
|
Err(_) if value.value().chars().all(|c| c.is_ascii_uppercase()) => {
|
|
|
|
Ok(MethodTypeExt::Custom(value.clone()))
|
|
|
|
}
|
|
|
|
Err(err) => Err(err),
|
|
|
|
}
|
2020-09-16 23:37:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
struct Args {
|
|
|
|
path: syn::LitStr,
|
2021-03-04 13:38:47 +01:00
|
|
|
resource_name: Option<syn::LitStr>,
|
2022-06-06 19:53:23 +02:00
|
|
|
guards: Vec<Path>,
|
2020-05-07 11:31:12 +02:00
|
|
|
wrappers: Vec<syn::Type>,
|
2023-02-06 13:40:41 +01:00
|
|
|
methods: HashSet<MethodTypeExt>,
|
2019-10-14 17:34:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Args {
|
2020-09-22 23:42:51 +02:00
|
|
|
fn new(args: AttributeArgs, method: Option<MethodType>) -> syn::Result<Self> {
|
2019-10-14 17:34:17 +02:00
|
|
|
let mut path = None;
|
2021-03-04 13:38:47 +01:00
|
|
|
let mut resource_name = None;
|
2019-10-14 17:34:17 +02:00
|
|
|
let mut guards = Vec::new();
|
2020-05-07 11:31:12 +02:00
|
|
|
let mut wrappers = Vec::new();
|
2020-09-16 23:37:41 +02:00
|
|
|
let mut methods = HashSet::new();
|
2020-09-22 23:42:51 +02:00
|
|
|
|
2022-07-04 06:31:49 +02:00
|
|
|
if args.is_empty() {
|
|
|
|
return Err(syn::Error::new(
|
|
|
|
Span::call_site(),
|
|
|
|
format!(
|
|
|
|
r#"invalid service definition, expected #[{}("<path>")]"#,
|
|
|
|
method
|
|
|
|
.map_or("route", |it| it.as_str())
|
|
|
|
.to_ascii_lowercase()
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2020-09-22 23:42:51 +02:00
|
|
|
let is_route_macro = method.is_none();
|
|
|
|
if let Some(method) = method {
|
2023-02-06 18:06:47 +01:00
|
|
|
methods.insert(MethodTypeExt::Standard(method));
|
2020-09-22 23:42:51 +02:00
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
for arg in args {
|
|
|
|
match arg {
|
|
|
|
NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
|
|
|
|
None => {
|
2021-08-30 22:50:40 +02:00
|
|
|
let _ = ResourceDef::new(lit.value());
|
2019-10-14 17:34:17 +02:00
|
|
|
path = Some(lit);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
lit,
|
|
|
|
"Multiple paths specified! Should be only one!",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
},
|
2023-02-06 18:06:47 +01:00
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
|
2021-03-04 13:38:47 +01:00
|
|
|
if nv.path.is_ident("name") {
|
|
|
|
if let syn::Lit::Str(lit) = nv.lit {
|
|
|
|
resource_name = Some(lit);
|
|
|
|
} else {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
nv.lit,
|
|
|
|
"Attribute name expects literal string!",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
} else if nv.path.is_ident("guard") {
|
2019-10-14 17:34:17 +02:00
|
|
|
if let syn::Lit::Str(lit) = nv.lit {
|
2022-06-06 19:53:23 +02:00
|
|
|
guards.push(lit.parse::<Path>()?);
|
2019-10-14 17:34:17 +02:00
|
|
|
} else {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
nv.lit,
|
|
|
|
"Attribute guard expects literal string!",
|
|
|
|
));
|
|
|
|
}
|
2020-05-07 11:31:12 +02:00
|
|
|
} else if nv.path.is_ident("wrap") {
|
|
|
|
if let syn::Lit::Str(lit) = nv.lit {
|
|
|
|
wrappers.push(lit.parse()?);
|
|
|
|
} else {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
nv.lit,
|
|
|
|
"Attribute wrap expects type",
|
|
|
|
));
|
|
|
|
}
|
2020-09-16 23:37:41 +02:00
|
|
|
} else if nv.path.is_ident("method") {
|
2020-09-22 23:42:51 +02:00
|
|
|
if !is_route_macro {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
&nv,
|
|
|
|
"HTTP method forbidden here. To handle multiple methods, use `route` instead",
|
|
|
|
));
|
|
|
|
} else if let syn::Lit::Str(ref lit) = nv.lit {
|
2023-02-06 18:06:47 +01:00
|
|
|
if !methods.insert(MethodTypeExt::try_from(lit)?) {
|
2020-09-16 23:37:41 +02:00
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
&nv.lit,
|
2023-02-06 18:06:47 +01:00
|
|
|
format!(
|
2020-09-22 23:42:51 +02:00
|
|
|
"HTTP method defined more than once: `{}`",
|
2020-09-16 23:37:41 +02:00
|
|
|
lit.value()
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
nv.lit,
|
|
|
|
"Attribute method expects literal string!",
|
|
|
|
));
|
|
|
|
}
|
2019-10-14 17:34:17 +02:00
|
|
|
} else {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
nv.path,
|
2020-09-16 23:37:41 +02:00
|
|
|
"Unknown attribute key is specified. Allowed: guard, method and wrap",
|
2019-10-14 17:34:17 +02:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
2023-02-06 18:06:47 +01:00
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
arg => {
|
2020-03-14 23:23:28 +01:00
|
|
|
return Err(syn::Error::new_spanned(arg, "Unknown attribute."));
|
2019-10-14 17:34:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-06 18:06:47 +01:00
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
Ok(Args {
|
|
|
|
path: path.unwrap(),
|
2021-03-04 13:38:47 +01:00
|
|
|
resource_name,
|
2019-10-14 17:34:17 +02:00
|
|
|
guards,
|
2020-05-07 11:31:12 +02:00
|
|
|
wrappers,
|
2020-09-16 23:37:41 +02:00
|
|
|
methods,
|
2019-10-14 17:34:17 +02:00
|
|
|
})
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
pub struct Route {
|
2022-07-04 06:31:49 +02:00
|
|
|
/// Name of the handler function being annotated.
|
2019-10-14 17:34:17 +02:00
|
|
|
name: syn::Ident,
|
2022-07-04 06:31:49 +02:00
|
|
|
|
|
|
|
/// Args passed to routing macro.
|
|
|
|
///
|
|
|
|
/// When using `#[routes]`, this will contain args for each specific routing macro.
|
|
|
|
args: Vec<Args>,
|
|
|
|
|
|
|
|
/// AST of the handler function being annotated.
|
2019-10-14 17:34:17 +02:00
|
|
|
ast: syn::ItemFn,
|
2021-02-24 13:26:56 +01:00
|
|
|
|
|
|
|
/// The doc comment attributes to copy to generated struct, if any.
|
|
|
|
doc_attributes: Vec<syn::Attribute>,
|
2019-10-14 17:34:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Route {
|
|
|
|
pub fn new(
|
|
|
|
args: AttributeArgs,
|
2021-10-19 18:30:32 +02:00
|
|
|
ast: syn::ItemFn,
|
2020-09-22 23:42:51 +02:00
|
|
|
method: Option<MethodType>,
|
2019-10-14 17:34:17 +02:00
|
|
|
) -> syn::Result<Self> {
|
|
|
|
let name = ast.sig.ident.clone();
|
2019-03-31 09:23:15 +02:00
|
|
|
|
2021-10-19 18:30:32 +02:00
|
|
|
// 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.
|
2021-02-24 13:26:56 +01:00
|
|
|
let doc_attributes = ast
|
|
|
|
.attrs
|
|
|
|
.iter()
|
|
|
|
.filter(|attr| attr.path.is_ident("doc"))
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
|
|
|
|
2020-09-22 23:42:51 +02:00
|
|
|
let args = Args::new(args, method)?;
|
2022-07-04 06:31:49 +02:00
|
|
|
|
2020-09-22 23:42:51 +02:00
|
|
|
if args.methods.is_empty() {
|
2020-09-16 23:37:41 +02:00
|
|
|
return Err(syn::Error::new(
|
|
|
|
Span::call_site(),
|
|
|
|
"The #[route(..)] macro requires at least one `method` attribute",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2022-07-04 06:31:49 +02:00
|
|
|
if matches!(ast.sig.output, syn::ReturnType::Default) {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
ast,
|
|
|
|
"Function has no return type. Cannot be used as handler",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
name,
|
|
|
|
args: vec![args],
|
|
|
|
ast,
|
|
|
|
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();
|
|
|
|
|
|
|
|
if matches!(ast.sig.output, syn::ReturnType::Default) {
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
ast,
|
|
|
|
"Function has no return type. Cannot be used as handler",
|
|
|
|
));
|
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
Ok(Self {
|
2019-03-31 09:23:15 +02:00
|
|
|
name,
|
2019-10-14 17:34:17 +02:00
|
|
|
args,
|
2019-03-31 09:23:15 +02:00
|
|
|
ast,
|
2021-02-24 13:26:56 +01:00
|
|
|
doc_attributes,
|
2019-10-14 17:34:17 +02:00
|
|
|
})
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
2020-03-14 23:23:28 +01:00
|
|
|
}
|
2019-03-31 09:23:15 +02:00
|
|
|
|
2020-03-14 23:23:28 +01:00
|
|
|
impl ToTokens for Route {
|
|
|
|
fn to_tokens(&self, output: &mut TokenStream2) {
|
|
|
|
let Self {
|
|
|
|
name,
|
|
|
|
ast,
|
2022-07-04 06:31:49 +02:00
|
|
|
args,
|
|
|
|
doc_attributes,
|
|
|
|
} = self;
|
|
|
|
|
|
|
|
let registrations: TokenStream2 = args
|
|
|
|
.iter()
|
|
|
|
.map(|args| {
|
|
|
|
let Args {
|
2020-07-22 01:28:33 +02:00
|
|
|
path,
|
2021-03-04 13:38:47 +01:00
|
|
|
resource_name,
|
2020-07-22 01:28:33 +02:00
|
|
|
guards,
|
|
|
|
wrappers,
|
2020-09-16 23:37:41 +02:00
|
|
|
methods,
|
2022-07-04 06:31:49 +02:00
|
|
|
} = args;
|
|
|
|
|
|
|
|
let resource_name = resource_name
|
|
|
|
.as_ref()
|
|
|
|
.map_or_else(|| name.to_string(), LitStr::value);
|
|
|
|
|
2023-02-06 18:06:47 +01:00
|
|
|
let method_guards = {
|
2023-02-13 22:09:20 +01:00
|
|
|
debug_assert!(!methods.is_empty(), "Args::methods should not be empty");
|
2023-02-06 18:06:47 +01:00
|
|
|
|
|
|
|
let mut others = methods.iter();
|
|
|
|
let first = others.next().unwrap();
|
|
|
|
|
|
|
|
if methods.len() > 1 {
|
|
|
|
let other_method_guards = others
|
|
|
|
.map(|method_ext| method_ext.to_tokens_multi_guard_or_chain())
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
first.to_tokens_multi_guard(other_method_guards)
|
|
|
|
} else {
|
|
|
|
first.to_tokens_single_guard()
|
2022-07-04 06:31:49 +02:00
|
|
|
}
|
2023-02-06 18:06:47 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
let __resource = ::actix_web::Resource::new(#path)
|
|
|
|
.name(#resource_name)
|
|
|
|
#method_guards
|
|
|
|
#(.guard(::actix_web::guard::fn_guard(#guards)))*
|
|
|
|
#(.wrap(#wrappers))*
|
|
|
|
.to(#name);
|
|
|
|
::actix_web::dev::HttpServiceFactory::register(__resource, __config);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
2020-09-16 23:37:41 +02:00
|
|
|
|
2019-10-14 17:34:17 +02:00
|
|
|
let stream = quote! {
|
2021-02-24 13:26:56 +01:00
|
|
|
#(#doc_attributes)*
|
2020-02-22 10:32:12 +01:00
|
|
|
#[allow(non_camel_case_types, missing_docs)]
|
2019-10-14 17:34:17 +02:00
|
|
|
pub struct #name;
|
|
|
|
|
2022-01-19 23:23:53 +01:00
|
|
|
impl ::actix_web::dev::HttpServiceFactory for #name {
|
2020-01-25 23:22:40 +01:00
|
|
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
2019-10-14 17:34:17 +02:00
|
|
|
#ast
|
2022-07-04 06:31:49 +02:00
|
|
|
#registrations
|
2019-10-14 17:34:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-03-14 23:23:28 +01:00
|
|
|
|
|
|
|
output.extend(stream);
|
2019-03-31 09:23:15 +02:00
|
|
|
}
|
|
|
|
}
|
2020-03-19 20:40:42 +01:00
|
|
|
|
2020-09-22 23:42:51 +02:00
|
|
|
pub(crate) fn with_method(
|
|
|
|
method: Option<MethodType>,
|
2020-03-19 20:40:42 +01:00
|
|
|
args: TokenStream,
|
|
|
|
input: TokenStream,
|
|
|
|
) -> TokenStream {
|
|
|
|
let args = parse_macro_input!(args as syn::AttributeArgs);
|
2021-10-19 18:30:32 +02:00
|
|
|
|
|
|
|
let 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),
|
|
|
|
};
|
|
|
|
|
|
|
|
match Route::new(args, ast, method) {
|
2020-03-19 20:40:42 +01:00
|
|
|
Ok(route) => route.into_token_stream().into(),
|
2021-10-19 18:30:32 +02:00
|
|
|
// on macro related error, make IDEs happy; see fn docs
|
2021-10-14 19:06:31 +02:00
|
|
|
Err(err) => input_and_compile_error(input, err),
|
2020-03-19 20:40:42 +01:00
|
|
|
}
|
|
|
|
}
|
2021-10-14 19:06:31 +02:00
|
|
|
|
2022-07-04 06:31:49 +02:00
|
|
|
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) = ast
|
|
|
|
.attrs
|
|
|
|
.into_iter()
|
|
|
|
.map(|attr| match MethodType::from_path(&attr.path) {
|
|
|
|
Ok(method) => Ok((method, attr)),
|
|
|
|
Err(_) => Err(attr),
|
|
|
|
})
|
|
|
|
.partition::<Vec<_>, _>(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::<Result<Vec<_>, _>>()
|
|
|
|
{
|
|
|
|
Ok(methods) if methods.is_empty() => return input_and_compile_error(
|
|
|
|
input,
|
|
|
|
syn::Error::new(
|
|
|
|
Span::call_site(),
|
|
|
|
"The #[routes] macro requires at least one `#[<method>(..)]` attribute.",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-14 19:06:31 +02:00
|
|
|
/// 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
|
|
|
|
/// recover and show more precise errors within the macro body.
|
|
|
|
///
|
|
|
|
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
|
|
|
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
|
|
|
let compile_err = TokenStream::from(err.to_compile_error());
|
|
|
|
item.extend(compile_err);
|
2021-10-19 02:32:58 +02:00
|
|
|
item
|
2021-10-14 19:06:31 +02:00
|
|
|
}
|