use proc_macro2::TokenStream;
use quote::format_ident;
use syn::{Attribute, Ident, ItemFn, LitStr, Token};
pub struct RouteAttrArgs {
pub path: LitStr,
pub name_override: Option<LitStr>,
}
impl RouteAttrArgs {
pub fn helper_ident(&self, handler_name: &Ident) -> Ident {
self.name_override.as_ref().map_or_else(
|| handler_name.clone(),
|lit| format_ident!("{}", lit.value()),
)
}
}
impl syn::parse::Parse for RouteAttrArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let path: LitStr = input.parse()?;
let name_override = if input.peek(Token![,]) {
let _comma: Token![,] = input.parse()?;
let key: Ident = input.parse()?;
if key != "name" {
return Err(syn::Error::new(
key.span(),
format!(
"unknown route attribute key `{key}`. \
Supported keys: `name`."
),
));
}
let _eq: Token![=] = input.parse()?;
Some(input.parse::<LitStr>()?)
} else {
None
};
Ok(Self {
path,
name_override,
})
}
}
pub fn parse_route_attr(attr: TokenStream) -> Result<RouteAttrArgs, TokenStream> {
let args: RouteAttrArgs = syn::parse2(attr).map_err(|err| err.to_compile_error())?;
validate_path(&args.path)?;
if let Some(ref name_lit) = args.name_override {
syn::parse_str::<Ident>(&name_lit.value()).map_err(|_| {
syn::Error::new(
name_lit.span(),
format!(
"route `name` override {:?} is not a valid Rust identifier",
name_lit.value()
),
)
.to_compile_error()
})?;
}
Ok(args)
}
pub fn parse_route_path(attr: TokenStream) -> Result<LitStr, TokenStream> {
let path: LitStr = syn::parse2(attr).map_err(|err| err.to_compile_error())?;
validate_path(&path)?;
Ok(path)
}
fn validate_path(path: &LitStr) -> Result<(), TokenStream> {
if path.value().is_empty() {
return Err(syn::Error::new(path.span(), "Route path must not be empty").to_compile_error());
}
if !path.value().starts_with('/') {
let suggested = format!("/{}", path.value());
return Err(syn::Error::new(
path.span(),
format!("Route path must start with '/'. Did you mean \"{suggested}\"?"),
)
.to_compile_error());
}
Ok(())
}
pub fn parse_async_handler(item: TokenStream) -> Result<ItemFn, TokenStream> {
let input_fn: ItemFn = syn::parse2(item.clone()).map_err(|_| {
syn::Error::new_spanned(item, "route macros can only be applied to functions")
.to_compile_error()
})?;
if input_fn.sig.asyncness.is_none() {
return Err(syn::Error::new_spanned(
input_fn.sig.fn_token,
"Autumn route handlers must be async functions",
)
.to_compile_error());
}
Ok(input_fn)
}
pub fn extract_interceptors(attrs: &mut Vec<Attribute>) -> Vec<syn::Path> {
let mut interceptors = Vec::new();
attrs.retain(|attr| {
if attr.path().is_ident("intercept") {
if let Ok(path) = attr.parse_args::<syn::Path>() {
interceptors.push(path);
}
false } else {
true }
});
interceptors
}