form_net/extract/macros.rs
1//! The `form!` macro.
2
3/// Declares a struct and its [`FromForm`](crate::FromForm) implementation.
4///
5/// - The struct is emitted exactly as written (attributes, doc comments,
6/// visibility); it may not be generic and must have named fields.
7/// - A field's wire name is its Rust name; `= "wire-name"` after the type
8/// overrides it. Bracket names such as `tags[]` are plain names, matched
9/// byte for byte, never expanded into a structure.
10/// - The optional `ignore = [ ... ];` after the struct lists names that are
11/// accepted and dropped (the anti-forgery field and `_charset_` are the
12/// usual ones). Every other undeclared name is refused.
13/// - Field types implement [`FromField`](crate::FromField); see the value
14/// rules in `docs/API.md`.
15///
16/// Matching a name is a chain of byte comparisons the compiler turns into a
17/// length switch: no hash map, no allocation beyond what the field types
18/// own.
19///
20/// # Examples
21///
22/// ```
23/// use form_net::FromForm;
24///
25/// form_net::form! {
26/// /// The sign-up form.
27/// #[derive(Debug)]
28/// pub struct Signup {
29/// pub email: String,
30/// pub password: form_net::Secret,
31/// pub display_name: Option<String> = "display-name",
32/// pub age: u8,
33/// pub newsletter: bool,
34/// pub tags: Vec<String> = "tags[]",
35/// }
36/// ignore = ["csrf_token", "_charset_"];
37/// }
38///
39/// let s = Signup::from_urlencoded(b"email=a%40b.example&password=pw&age=30&tags[]=x&tags[]=y&csrf_token=t")?;
40/// assert_eq!((s.age, s.newsletter, s.tags.len()), (30, false, 2));
41/// assert!(s.display_name.is_none());
42///
43/// let err = Signup::from_urlencoded(b"email=a&password=p&age=300").unwrap_err();
44/// assert_eq!(err.code(), "extract.invalid");
45/// assert!(Signup::from_urlencoded(b"email=a&password=p&age=1&admin=1").is_err());
46/// # Ok::<(), form_net::FormError>(())
47/// ```
48#[macro_export]
49macro_rules! form {
50 (
51 $(#[$meta:meta])*
52 $vis:vis struct $name:ident {
53 $(
54 $(#[$fmeta:meta])*
55 $fvis:vis $field:ident : $ty:ty $(= $wire:literal)?
56 ),* $(,)?
57 }
58 $( ignore = [ $($ignored:literal),* $(,)? ]; )?
59 ) => {
60 $(#[$meta])*
61 $vis struct $name {
62 $(
63 $(#[$fmeta])*
64 $fvis $field: $ty,
65 )*
66 }
67
68 const _: () = {
69 #[doc(hidden)]
70 #[derive(Default)]
71 pub struct __FormNetBuilder {
72 $( $field: <$ty as $crate::FromField>::Slot, )*
73 }
74
75 impl $crate::FormBuilder for __FormNetBuilder {
76 type Output = $name;
77
78 fn text(&mut self, name: &str, value: &str) -> ::core::result::Result<(), $crate::FormError> {
79 $(
80 if name == $crate::__form_wire!($field $(, $wire)?) {
81 return <$ty as $crate::FromField>::accept_text(&mut self.$field, value)
82 .map_err(|e| $crate::__value_error(e, ::core::stringify!($field)));
83 }
84 )*
85 $( $( if name == $ignored { return ::core::result::Result::Ok(()); } )* )?
86 ::core::result::Result::Err($crate::__unknown())
87 }
88
89 fn file(&mut self, name: &str, file: $crate::FileValue<'_>) -> ::core::result::Result<(), $crate::FormError> {
90 $(
91 if name == $crate::__form_wire!($field $(, $wire)?) {
92 return <$ty as $crate::FromField>::accept_file(&mut self.$field, file)
93 .map_err(|e| $crate::__value_error(e, ::core::stringify!($field)));
94 }
95 )*
96 $( $( if name == $ignored { return ::core::result::Result::Ok(()); } )* )?
97 let _ = file;
98 ::core::result::Result::Err($crate::__unknown())
99 }
100
101 fn finish(self) -> ::core::result::Result<$name, $crate::FormError> {
102 ::core::result::Result::Ok($name {
103 $(
104 $field: <$ty as $crate::FromField>::finish(self.$field)
105 .map_err(|e| $crate::__value_error(e, ::core::stringify!($field)))?,
106 )*
107 })
108 }
109 }
110
111 impl $crate::FromForm for $name {
112 type Builder = __FormNetBuilder;
113 }
114 };
115 };
116}
117
118/// The wire name of a `form!` field. Not part of the API.
119#[doc(hidden)]
120#[macro_export]
121macro_rules! __form_wire {
122 ($field:ident) => {
123 ::core::stringify!($field)
124 };
125 ($field:ident, $wire:literal) => {
126 $wire
127 };
128}