fix(derive): reject form type impostors

Tighten generated form-handler validation so only actual Form paths satisfy form handles; names like CreateForm no longer bypass the slhx::Form<_> requirement. Cover with unit and compile-fail tests.

req: form/004

req: form/006

req: test/003
This commit is contained in:
slhx agent
2026-05-25 22:10:19 +02:00
parent 00af1017cc
commit 6d9d5e4c4e
2 changed files with 73 additions and 10 deletions
+19 -10
View File
@@ -120,11 +120,18 @@ fn generated_path(file: &str) -> Option<PathBuf> {
fn has_form_param(function: &ItemFn) -> bool {
function.sig.inputs.iter().any(|arg| match arg {
FnArg::Typed(arg) => type_ends_with(&arg.ty, "Form"),
FnArg::Typed(arg) => is_form_type(&arg.ty),
FnArg::Receiver(_) => false,
})
}
fn is_form_type(ty: &Type) -> bool {
match ty {
Type::Path(path) => path.path.segments.last().is_some_and(|segment| segment.ident == "Form"),
_ => false,
}
}
fn has_non_unit_return(function: &ItemFn) -> bool {
match &function.sig.output {
ReturnType::Default => false,
@@ -132,13 +139,6 @@ fn has_non_unit_return(function: &ItemFn) -> bool {
}
}
fn type_ends_with(ty: &Type, ident: &str) -> bool {
match ty {
Type::Path(path) => path.path.segments.last().is_some_and(|segment| segment.ident == ident),
_ => false,
}
}
fn syms_contains_handle(path: &PathBuf, ident: &str) -> bool {
let Ok(syms) = std::fs::read_to_string(path) else {
return true;
@@ -169,8 +169,8 @@ fn compile_error(message: &str) -> TokenStream {
#[cfg(test)]
mod tests {
use super::{handle_requires_form, has_form_param, has_non_unit_return, syms_contains_handle};
use syn::parse_quote;
use super::{handle_requires_form, has_form_param, has_non_unit_return, is_form_type, syms_contains_handle};
use syn::{parse_quote, Type};
#[test]
fn syms_lookup_matches_handle_ident() {
@@ -201,4 +201,13 @@ mod tests {
assert!(!has_form_param(&empty));
assert!(!has_non_unit_return(&empty));
}
#[test]
fn form_param_matches_form_type_not_name_suffix() {
let real_form: Type = parse_quote!(slhx::Form<CreateTodo>);
let impostor: Type = parse_quote!(CreateTodoForm);
assert!(is_form_type(&real_form));
assert!(!is_form_type(&impostor));
}
}