use std::collections::HashMap;
use proc_macro::Span;
use syn::{spanned::Spanned, Error, FnArg, ItemFn, Lit, Meta, NestedMeta, Pat, ReturnType};
use super::{
models::{Item, ParamInfo, RouteInfo},
utils::get_type,
};
pub fn handle_fn(attrs: &[NestedMeta], item: ItemFn) -> Result<Item, Error> {
let mut base = "".to_string();
for attr in attrs.iter() {
if let NestedMeta::Lit(Lit::Str(lit)) = attr {
if !base.is_empty() {
return Err(Error::new(
attr.span(),
"Duplicate arguments for route base path",
));
}
base = lit.value();
}
}
let attr = item
.attrs
.iter()
.find(|a| {
a.path.is_ident("get")
|| a.path.is_ident("post")
|| a.path.is_ident("patch")
|| a.path.is_ident("delete")
|| a.path.is_ident("push")
})
.ok_or_else(|| Error::new(item.span(), "Could not find rocket method attribute"))?;
let method = attr
.path
.get_ident()
.expect("Ident removed itself")
.to_string()
.to_uppercase();
let return_type = match item.sig.output {
ReturnType::Default => None,
ReturnType::Type(_, ty) => Some(get_type(&ty)?),
};
let mut params = HashMap::new();
for param in item.sig.inputs {
if let FnArg::Typed(param) = param {
if let Pat::Ident(ident) = *param.pat {
let name = ident.ident.to_string();
if let Ok(param_type) = get_type(¶m.ty) {
params.insert(name, param_type);
} else {
params.insert(name, "Guard".to_string());
}
}
}
}
let mut metas = if let Meta::List(metas) = attr.parse_meta()? {
metas.nested.into_iter()
} else {
return Err(Error::new(attr.span(), "Cannot parse rocket macro"));
};
let route = if let NestedMeta::Lit(Lit::Str(route)) = metas
.next()
.ok_or_else(|| Error::new(attr.span(), "Could not find route in rocket macro"))?
{
route.value()
} else {
return Err(Error::new(
attr.span(),
"Could not find route in rocket macro",
));
};
let mut path_params = vec![];
let mut query_params = vec![];
let (path, query) = route.split_once('?').unwrap_or((&route, ""));
for segment in path.split('/') {
if segment.starts_with('<') && segment.ends_with('>') {
let name = segment[1..segment.len() - 1].to_string();
let param_type = params.remove(&name).ok_or_else(|| {
Error::new(
Span::call_site().into(),
format!("Cannot find type of path param {}", name),
)
})?;
path_params.push(ParamInfo { param_type, name });
}
}
for param in query.split('&') {
if param.starts_with('<') && param.ends_with('>') {
let name = param[1..param.len() - 1].to_string();
let param_type = params.remove(&name).ok_or_else(|| {
Error::new(
Span::call_site().into(),
format!("Cannot find type of query param {}", name),
)
})?;
query_params.push(ParamInfo { param_type, name });
}
}
let mut route = format!("{}{}", base, route);
if route != "/" {
if let Some(new_route) = route.strip_suffix('/') {
route = new_route.to_string();
}
}
let mut body_type = None;
for meta in metas {
if let NestedMeta::Meta(Meta::NameValue(meta)) = meta {
if meta.path.is_ident("data") {
if let Lit::Str(lit) = meta.lit {
let value = lit.value();
body_type = Some(params.remove(&value[1..value.len() - 1]).unwrap());
} }
}
}
Ok(Item::Route(RouteInfo {
method,
route,
path_params,
query_params,
body_type,
return_type,
guards: params.into_keys().collect(),
}))
}