use syn::{
Attribute, Error, FnArg, GenericArgument, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,
NestedMeta, Pat, PatIdent, PatType, PathArguments, PathSegment, Result, ReturnType, Type,
TypePath,
};
#[derive(Clone, Debug)]
pub enum ArgumentType {
SelfType,
Request,
Query,
Json,
Form,
Params {
is_query_param: bool,
is_string: bool,
},
Cookie,
Option(Box<ArgumentType>),
}
impl ArgumentType {
pub fn new(attrs: &HandlerAttrs, name: &str, p: &PathSegment) -> Result<Self> {
let typ_ident_str = p.ident.to_string();
match typ_ident_str.as_str() {
"Request" => Ok(ArgumentType::Request),
"CookieJar" => Ok(ArgumentType::Cookie),
"Query" => Ok(ArgumentType::Query),
"Json" => Ok(ArgumentType::Json),
"Form" => Ok(ArgumentType::Form),
"Option" => {
if let PathArguments::AngleBracketed(a) = &p.arguments {
let a = a.args.first().ok_or_else(|| {
Error::new_spanned(a, "Option types need an type argument")
})?;
if let GenericArgument::Type(Type::Path(t)) = a {
let p2 = t.path.segments.first().ok_or_else(|| {
Error::new_spanned(a, "Option types need an type path argument")
})?;
return Ok(ArgumentType::Option(Box::new(ArgumentType::new(
attrs, name, p2,
)?)));
}
}
Err(Error::new_spanned(p, "Invalid option type"))
}
_params => Ok(ArgumentType::Params {
is_query_param: !attrs.method_path.iter().any(|(_, path)| {
let mut uri = String::new();
uri.push('{');
uri.push_str(name);
uri.push('}');
path.contains(&uri)
}),
is_string: typ_ident_str.eq("String"),
}),
}
}
}
#[derive(Clone)]
pub struct FunctionArgument {
pub name: String,
pub typ: Option<TypePath>,
pub a_type: ArgumentType,
#[cfg(feature = "validate")]
pub validated: bool,
#[cfg(feature = "validate")]
pub is_vec: bool,
}
impl FunctionArgument {
pub fn new(attrs: &HandlerAttrs, a: &FnArg) -> Result<FunctionArgument> {
match a {
FnArg::Receiver(r) => {
if r.mutability.is_some() {
return Err(Error::new_spanned(
r,
"Controller references are immutable static references, remove 'mut'",
));
}
if r.reference.is_none() {
return Err(Error::new_spanned(
r,
"Controller cannot be passed as owned value, please use &self",
));
}
Ok(FunctionArgument {
name: "self".to_string(),
typ: None,
a_type: ArgumentType::SelfType,
#[cfg(feature = "validate")]
validated: false,
#[cfg(feature = "validate")]
is_vec: false,
})
}
FnArg::Typed(t) => match t.pat.as_ref() {
Pat::Ident(i) => Self::parse_pat_ident(attrs, t, i),
Pat::Reference(r) => Err(Error::new_spanned(
r.and_token,
"Unexpected referece, help: remove '&'",
)),
_ => Err(Error::new_spanned(t, "Unexpected handler argument format")),
},
}
}
fn parse_pat_ident(attrs: &HandlerAttrs, t: &PatType, i: &PatIdent) -> Result<Self> {
let name = i.ident.to_string();
if let Some(rf) = i.by_ref {
return Err(Error::new_spanned(
rf,
"Invalid handler argument, consider removing 'ref'",
));
}
if let Type::Path(p) = t.ty.as_ref() {
let typ = Some(p.clone());
let p = p.path.segments.first().ok_or_else(|| {
Error::new_spanned(p, "Invalid handler argument, argument should have an ident")
})?;
let a_type = ArgumentType::new(attrs, &name, p)?;
#[cfg(feature = "validate")]
{
let validated = !attrs.validator_exclusions.contains(&name);
let mut is_vec = false;
let typ_ident_str = p.ident.to_string();
let mut validated_type = None;
match typ_ident_str.as_str() {
"Query" => {
if let PathArguments::AngleBracketed(a) = &p.arguments {
let a = a.args.first().ok_or_else(|| {
Error::new_spanned(a, "Query types need an type argument")
})?;
if let GenericArgument::Type(Type::Path(t)) = a {
validated_type = t.path.segments.first().map(|p2| p2.ident.to_string());
}
}
}
"Json" => {
if let PathArguments::AngleBracketed(a) = &p.arguments {
let a = a.args.first().ok_or_else(|| {
Error::new_spanned(a, "Json types need an type argument")
})?;
if let GenericArgument::Type(Type::Path(t)) = a {
validated_type = t.path.segments.first().map(|p2| p2.ident.to_string());
}
}
}
"Form" => {
if let PathArguments::AngleBracketed(a) = &p.arguments {
let a = a.args.first().ok_or_else(|| {
Error::new_spanned(a, "Form types need an type argument")
})?;
if let GenericArgument::Type(Type::Path(t)) = a {
validated_type = t.path.segments.first().map(|p2| p2.ident.to_string());
}
}
}
_ => (),
};
if let Some("Vec") = validated_type.as_deref() {
is_vec = true;
}
return Ok(FunctionArgument {
name,
typ,
a_type,
validated,
is_vec,
});
}
#[cfg(not(feature = "validate"))]
{
return Ok(FunctionArgument { name, typ, a_type });
}
} else if let Type::Reference(r) = t.ty.as_ref() {
return Err(Error::new_spanned(
r.and_token,
"Unexpected reference, help: remove '&'",
));
}
Err(Error::new_spanned(
i,
"Invalid handler argument, argument should be TypePath",
))
}
pub fn is_string(&self) -> bool {
match self.a_type {
ArgumentType::Params { is_string, .. } => is_string,
_ => false,
}
}
}
#[derive(Clone)]
pub struct HandlerOption {
pub sync_handler: bool,
pub request_unused: bool,
pub parse_cookies: bool,
pub parse_query: bool,
pub fn_arguments: Vec<FunctionArgument>,
}
impl HandlerOption {
pub fn new(attrs: &HandlerAttrs, m: &ImplItemMethod) -> Result<Self> {
let mut sync_handler = false;
let mut request_unused = true;
let mut parse_query = false;
let mut parse_cookies = attrs.cookie;
if m.sig.asyncness.is_none() {
sync_handler = true
}
let fn_arguments = m
.sig
.inputs
.iter()
.map(|fn_a| FunctionArgument::new(attrs, fn_a))
.collect::<Result<Vec<FunctionArgument>>>()?;
fn_arguments.iter().for_each(|a_repr| match &a_repr.a_type {
ArgumentType::Cookie => parse_cookies = true,
ArgumentType::Params {
is_query_param: true,
..
} => parse_query = true,
ArgumentType::Option(inner) => {
if let ArgumentType::Params {
is_query_param: true,
..
} = inner.as_ref()
{
parse_query = true;
}
}
_ => {}
});
let req_param: Option<&FunctionArgument> = fn_arguments.iter().find(|a_repr| {
if let ArgumentType::Request = a_repr.a_type {
true
} else {
false
}
});
if req_param.is_some(){
request_unused = false;
}
Ok(Self {
sync_handler,
request_unused,
parse_cookies,
parse_query,
fn_arguments,
})
}
pub fn needs_wrapper_fn(&self) -> bool {
self.sync_handler
|| self.request_unused
|| self.parse_query
|| self.parse_cookies
|| self.fn_arguments.len() > 2
}
}
#[derive(Clone)]
pub struct HandlerAttrs {
pub method_path: Vec<(String, String)>,
pub cookie: bool,
pub validator_exclusions: Vec<String>,
}
#[derive(Clone)]
pub struct Handler {
pub attrs: HandlerAttrs,
pub method: ImplItemMethod,
pub return_type: Box<Type>,
pub option: HandlerOption,
}
impl Handler {
pub fn new(mut m: ImplItemMethod) -> Result<Self> {
let attrs = HandlerAttrs::new(std::mem::take(&mut m.attrs), &m)?;
let wrapper_options = HandlerOption::new(&attrs, &m)?;
let return_type = if let ReturnType::Type(_0, typ) = &m.sig.output {
typ.clone()
} else {
return Err(Error::new_spanned(m.sig, "Invalid handler return type"));
};
Ok(Handler {
attrs,
method: m,
return_type,
option: wrapper_options,
})
}
}
impl HandlerAttrs {
fn empty_with_capacity(capacity: usize) -> Self {
Self {
method_path: Vec::with_capacity(capacity),
cookie: false,
validator_exclusions: Vec::new(),
}
}
pub fn new(attrs: Vec<Attribute>, method: &ImplItemMethod) -> Result<Self> {
let mut handler = HandlerAttrs::empty_with_capacity(attrs.len());
for attr in attrs {
let ident = match attr.path.get_ident() {
Some(i) if i == "cookie" => {
handler.cookie = true;
continue;
}
Some(i) => i,
None => continue,
};
let meta = attr.parse_meta()?;
match meta {
Meta::List(attribute) => {
if ident == "validator" {
#[cfg(not(feature = "validate"))]
{
return Err(Error::new_spanned(
ident,
"This requires the \"validate\" feature flag in athene",
));
}
#[cfg(feature = "validate")]
{
if attribute.nested.is_empty() {
return Err(Error::new_spanned(
ident,
"validator attribute cannot be empty",
));
}
for validator_attributes in &attribute.nested {
match validator_attributes {
NestedMeta::Meta(Meta::List(validator_attribute)) => {
let i = validator_attribute
.path
.get_ident()
.map(|i| i.to_string());
match i.as_deref() {
Some("exclude") => {
if validator_attribute.nested.is_empty() {
return Err(Error::new_spanned(validator_attribute, "validator exclude attribute cannot be empty"));
}
for excluded_meta in &validator_attribute.nested {
match excluded_meta {
NestedMeta::Lit(Lit::Str(excluded)) => {
handler.validator_exclusions.push(excluded.value());
}
_ => return Err(Error::new_spanned(validator_attribute, "Expected a list of quoted parameter names")),
}
}
}
_ => {
return Err(Error::new_spanned(
validator_attribute,
"Invalid validator attribute",
))
}
}
}
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("exclude") => {
return Err(Error::new_spanned(
p,
"expected a list of excluded parameters",
))
}
_ => {
return Err(Error::new_spanned(
validator_attributes,
"Invalid validator attribute",
))
}
}
}
}
} else {
if let Some(NestedMeta::Lit(Lit::Str(str))) = attribute.nested.first() {
let path = str.value();
if !path.starts_with('/') {
return Err(Error::new_spanned(str, "Path must start with '/'"));
}
let method = ident.to_string().to_uppercase();
handler.method_path.push((method, path));
} else {
return Err(Error::new_spanned(attribute, "Missing path for method"));
}
}
}
Meta::NameValue(_) => {}
Meta::Path(p) => {
if let Some(ident_str) = p.get_ident().map(|p| p.to_string()) {
if ident_str.starts_with("cookie") {
handler.cookie = true;
}
}
}
}
}
if handler.method_path.is_empty() {
return Err(Error::new_spanned(
&method.sig,
"Missing Router attribute for handler, help: adde something like `#[get(\"/\")]`",
));
}
Ok(handler)
}
}
pub fn parse_handlers(input: ItemImpl) -> Result<Vec<Handler>> {
input
.items
.into_iter()
.filter_map(|item| match item {
ImplItem::Method(m) => Some(Handler::new(m)),
_ => None,
})
.collect()
}