use crate::controller::handler::{Handler, HandlerAttrs};
use proc_macro2::{Ident, TokenStream};
use syn::{AttributeArgs, Error, ItemImpl, Lit, Meta, MetaNameValue, NestedMeta, Result};
use quote::{quote, ToTokens};
#[derive(Debug)]
pub struct ControllerAttr {
pub ident: Ident,
pub name: String,
pub version: Option<u16>,
pub prefix: Option<String>,
}
impl ControllerAttr {
pub fn new(args: AttributeArgs, input: &ItemImpl) -> Result<ControllerAttr> {
let mut name = None;
let mut version = None;
let mut prefix = None;
let ident = crate::util::parse_item_impl_ident(input)?;
for m in args.into_iter().filter_map(|a| {
if let NestedMeta::Meta(m) = a {
Some(m)
} else {
None
}
}) {
match m {
Meta::Path(p) => {
return Err(Error::new_spanned(
p,
"Unexpected Attribute on controller impl",
));
}
Meta::List(l) => {
return Err(Error::new_spanned(
l,
"Unexpected Attribute on controller impl",
));
}
Meta::NameValue(MetaNameValue { path, lit, .. }) => match (
path.segments
.first()
.map(|p| p.ident.to_string())
.as_deref(),
lit,
) {
(Some("name"), Lit::Str(bp)) => {
name = Some(bp.value().trim_matches('/').to_string());
}
(Some("version"), Lit::Str(v)) => {
version = Some(v.value().parse::<u16>().map_err(|_| {
Error::new_spanned(
v,
"Invalid version, expected number between 1 & u16::MAX",
)
})?);
}
(Some("version"), Lit::Int(v)) => {
version = Some(v.base10_parse::<u16>().map_err(|_| {
Error::new_spanned(
v,
"Invalid version, expected number between 1 & u16::MAX",
)
})?);
}
(Some("prefix"), Lit::Str(p)) => {
prefix = Some(p.value().trim_matches('/').to_string());
}
_ => {
return Err(Error::new_spanned(
path,
"Unexpected Param in controller macro",
));
}
},
}
}
let name = name.unwrap_or_else(|| {
ident
.to_string()
.to_lowercase()
.trim_end_matches("controller")
.to_string()
});
Ok(ControllerAttr {
ident: ident.clone(),
name,
version,
prefix,
})
}
}
pub fn gen_controller_trait_impl(attrs: &ControllerAttr, handlers: &[Handler]) -> TokenStream {
let base_path = gen_controller_base_path(attrs);
let handlers_fn = gen_controller_handler_fn(attrs, handlers);
let ident = &attrs.ident;
quote! {
impl Controller for #ident {
#base_path
#handlers_fn
}
}
}
fn gen_controller_base_path(attr: &ControllerAttr) -> TokenStream {
let mut path = "/".to_string();
if let Some(prefix) = attr.prefix.as_ref() {
path.push_str(prefix);
path.push('/');
}
if let Some(version) = attr.version {
path.push('v');
path.push_str(&version.to_string());
path.push('/');
}
path.push_str(attr.name.as_str());
quote! {
const BASE_PATH: &'static str = #path;
}
}
fn gen_controller_handler_fn(attr: &ControllerAttr, handlers: &[Handler]) -> TokenStream {
let mut handler_stream = TokenStream::new();
let attr_ident = &attr.ident;
for handler in handlers {
let HandlerAttrs {
method_path: methods_paths,
..
} = &handler.attrs;
let handler_ident = &handler.method.sig.ident;
for (method, path) in methods_paths {
let method = method.as_str();
let handler_name = handler_ident.to_string();
(quote! {
.add_with_name(#handler_name, #path, #method,#attr_ident::#handler_ident)
})
.to_tokens(&mut handler_stream);
}
}
quote! {
fn method(&self) -> Vec<ControllerMethod<Self>> where Self: Sized {
ControllerBuilder::new()
#handler_stream
.build()
}
}
}