1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! Shared helpers for attribute macros that inject hidden extractor
//! parameters (`#[secured]`, `#[authorize]`).
//!
//! Both macros want to add `__autumn_session: Session` and
//! `__autumn_state: State<AppState>` to a handler's argument list.
//! Stacking them is the documented common
//! pattern (`#[secured]` answers "are you in?", `#[authorize]`
//! answers "are you allowed?"), but each macro must avoid double-
//! injecting a parameter that the other already added — duplicate
//! parameter names are a compile error.
//!
//! Both attribute orderings need to work:
//!
//! - `#[secured]` outer / `#[authorize]` inner: `#[secured]` runs
//! first, injects `__autumn_session` and `__autumn_state`.
//! `#[authorize]` then runs on the modified function, sees the
//! existing parameters, and skips re-injection.
//! - `#[authorize]` outer / `#[secured]` inner: `#[authorize]`
//! runs first, injects `__autumn_session` and `__autumn_state`.
//! `#[secured]` then runs on the modified function, sees the
//! existing parameters, and skips re-injection.
use syn::ItemFn;
/// Return `true` when `func` already has a parameter bound to a
/// pattern with the given identifier name.
pub fn has_input_named(func: &ItemFn, name: &str) -> bool {
func.sig.inputs.iter().any(|arg| match arg {
syn::FnArg::Typed(pt) => pat_binds_name(&pt.pat, name),
syn::FnArg::Receiver(_) => false,
})
}
fn pat_binds_name(pat: &syn::Pat, name: &str) -> bool {
match pat {
syn::Pat::Ident(i) => i.ident == name,
// `State(__autumn_state)`: walk the inner pattern.
syn::Pat::TupleStruct(ts) => ts.elems.iter().any(|p| pat_binds_name(p, name)),
syn::Pat::Tuple(t) => t.elems.iter().any(|p| pat_binds_name(p, name)),
syn::Pat::Struct(s) => s.fields.iter().any(|fp| pat_binds_name(&fp.pat, name)),
syn::Pat::Reference(r) => pat_binds_name(&r.pat, name),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn detects_simple_ident_binding() {
let f: ItemFn = parse_quote! {
async fn h(__autumn_session: Session) {}
};
assert!(has_input_named(&f, "__autumn_session"));
assert!(!has_input_named(&f, "__autumn_state"));
}
#[test]
fn detects_tuple_struct_binding_for_state() {
let f: ItemFn = parse_quote! {
async fn h(State(__autumn_state): State<AppState>) {}
};
assert!(has_input_named(&f, "__autumn_state"));
assert!(!has_input_named(&f, "__autumn_session"));
}
#[test]
fn no_inputs_returns_false() {
let f: ItemFn = parse_quote! { async fn h() {} };
assert!(!has_input_named(&f, "__autumn_session"));
}
#[test]
fn finds_in_later_position() {
let f: ItemFn = parse_quote! {
async fn h(other: i32, __autumn_session: Session) {}
};
assert!(has_input_named(&f, "__autumn_session"));
}
}