use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Pat, Type};
pub fn levenshtein_distance(a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let len_a = a_chars.len();
let len_b = b_chars.len();
if len_a == 0 {
return len_b;
}
if len_b == 0 {
return len_a;
}
let mut matrix: Vec<Vec<usize>> = vec![vec![0; len_b + 1]; len_a + 1];
for (i, row) in matrix.iter_mut().enumerate().take(len_a + 1) {
row[0] = i;
}
for (j, cell) in matrix[0].iter_mut().enumerate() {
*cell = j;
}
for i in 1..=len_a {
for j in 1..=len_b {
let cost = if a_chars[i - 1] == b_chars[j - 1] {
0
} else {
1
};
matrix[i][j] = std::cmp::min(
std::cmp::min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1),
matrix[i - 1][j - 1] + cost,
);
}
}
matrix[len_a][len_b]
}
pub(crate) fn ferro() -> TokenStream2 {
quote!(::ferro)
}
pub(crate) enum ParamKind {
Request,
Primitive,
Model,
FormRequest,
}
pub(crate) fn extract_param_name(pat: &Pat) -> String {
match pat {
Pat::Ident(pat_ident) => pat_ident.ident.to_string(),
Pat::Wild(_) => "_".to_string(),
_ => "param".to_string(),
}
}
pub(crate) fn classify_param_type(ty: &Type) -> ParamKind {
match ty {
Type::Path(type_path) => {
let segments = &type_path.path.segments;
if segments.len() == 1 && segments[0].ident == "Request" {
return ParamKind::Request;
}
if segments.len() == 2 && segments[0].ident == "ferro" && segments[1].ident == "Request"
{
return ParamKind::Request;
}
if segments.len() == 1 {
let ident = segments[0].ident.to_string();
if is_primitive_type_name(&ident) {
return ParamKind::Primitive;
}
}
if let Some(last_segment) = segments.last() {
if last_segment.ident == "Model" && segments.len() >= 2 {
return ParamKind::Model;
}
}
ParamKind::FormRequest
}
_ => ParamKind::FormRequest,
}
}
pub(crate) fn is_primitive_type_name(name: &str) -> bool {
matches!(
name,
"i8" | "i16"
| "i32"
| "i64"
| "i128"
| "u8"
| "u16"
| "u32"
| "u64"
| "u128"
| "usize"
| "isize"
| "String"
)
}
pub(crate) fn generate_extraction(
ferro: &TokenStream2,
pat: &Pat,
ty: &Type,
param_name: &str,
kind: &ParamKind,
has_consumer: &mut bool,
has_request: &mut bool,
) -> TokenStream2 {
match kind {
ParamKind::Request => {
*has_request = true;
*has_consumer = true;
quote! {
let #pat: #ty = __ferro_req;
}
}
ParamKind::Primitive => {
quote! {
let #pat: #ty = {
let __value = __ferro_params.get(#param_name)
.ok_or_else(|| #ferro::FrameworkError::param(#param_name))?;
<#ty as #ferro::FromParam>::from_param(__value)?
};
}
}
ParamKind::Model => {
quote! {
let #pat: #ty = {
let __value = __ferro_params.get(#param_name)
.ok_or_else(|| #ferro::FrameworkError::param(#param_name))?;
<#ty as #ferro::AutoRouteBinding>::from_route_param(__value).await?
};
}
}
ParamKind::FormRequest => {
*has_consumer = true;
quote! {
let #pat: #ty = <#ty as #ferro::FromRequest>::from_request(__ferro_req).await?;
}
}
}
}