#![cfg(feature = "axum")]
use syn::{FnArg, GenericArgument, ItemFn, Pat, PathArguments, Type};
use super::path::Path;
pub(super) fn infer_path_parameters(item_fn: &ItemFn) -> Vec<Path> {
let mut result = Vec::new();
for arg in &item_fn.sig.inputs {
let FnArg::Typed(pt) = arg else { continue };
let Some(params) = extract_from_arg(&pt.pat, &pt.ty) else {
continue;
};
result.extend(params);
}
result
}
fn extract_from_arg(pat: &Pat, ty: &Type) -> Option<Vec<Path>> {
let inner_ty = unwrap_axum_path_type(ty)?;
let names = extract_names_from_path_pat(pat)?;
match inner_ty {
Type::Tuple(tuple) if names.len() == tuple.elems.len() => {
let mut params = Vec::with_capacity(names.len());
for (name, elem_ty) in names.into_iter().zip(tuple.elems.iter()) {
let schema = type_to_simple_path(elem_ty)?;
params.push(Path::new_inferred(name, schema));
}
Some(params)
}
Type::Tuple(_) => None,
single if names.len() == 1 => {
let schema = type_to_simple_path(single)?;
Some(vec![Path::new_inferred(
names.into_iter().next().unwrap(),
schema,
)])
}
_ => None,
}
}
fn unwrap_axum_path_type(ty: &Type) -> Option<&Type> {
let Type::Path(tp) = ty else { return None };
let last = tp.path.segments.last()?;
if last.ident != "Path" {
return None;
}
let PathArguments::AngleBracketed(args) = &last.arguments else {
return None;
};
if args.args.len() != 1 {
return None;
}
match args.args.first()? {
GenericArgument::Type(inner) => Some(inner),
_ => None,
}
}
fn extract_names_from_path_pat(pat: &Pat) -> Option<Vec<String>> {
let Pat::TupleStruct(ts) = pat else {
return None;
};
let last = ts.path.segments.last()?;
if last.ident != "Path" {
return None;
}
if ts.elems.len() != 1 {
return None;
}
match ts.elems.first()? {
Pat::Ident(pi) => Some(vec![pi.ident.to_string()]),
Pat::Tuple(pt) => pt
.elems
.iter()
.map(|e| match e {
Pat::Ident(pi) => Some(pi.ident.to_string()),
_ => None,
})
.collect(),
_ => None,
}
}
fn type_to_simple_path(ty: &Type) -> Option<syn::Path> {
match ty {
Type::Path(tp) if tp.qself.is_none() => Some(tp.path.clone()),
_ => None,
}
}