Skip to main content

form_net/extract/
value.rs

1//! `FromField` for the value types that ship: text, numbers, `bool`,
2//! `char`, `Option`, `Vec`.
3
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use super::{FileValue, FromField, ValueError};
8
9/// A scalar slot: a second value is `Duplicate`.
10fn set<T>(slot: &mut Option<T>, value: T) -> Result<(), ValueError> {
11    if slot.is_some() {
12        return Err(ValueError::Duplicate);
13    }
14    *slot = Some(value);
15    Ok(())
16}
17
18impl FromField for String {
19    type Slot = Option<String>;
20
21    fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
22        set(slot, String::from(value))
23    }
24
25    fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
26        slot.ok_or(ValueError::Missing)
27    }
28}
29
30/// ASCII digits only, at least one.
31fn digits(s: &str) -> bool {
32    !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
33}
34
35macro_rules! unsigned {
36    ($($t:ty),*) => {$(
37        impl FromField for $t {
38            type Slot = Option<$t>;
39
40            fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
41                if !digits(value) {
42                    return Err(ValueError::Invalid("not a whole number"));
43                }
44                let n = value.parse::<$t>().map_err(|_| ValueError::Invalid("out of range"))?;
45                set(slot, n)
46            }
47
48            fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
49                slot.ok_or(ValueError::Missing)
50            }
51        }
52    )*};
53}
54
55macro_rules! signed {
56    ($($t:ty),*) => {$(
57        impl FromField for $t {
58            type Slot = Option<$t>;
59
60            fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
61                if !digits(value.strip_prefix('-').unwrap_or(value)) {
62                    return Err(ValueError::Invalid("not a whole number"));
63                }
64                let n = value.parse::<$t>().map_err(|_| ValueError::Invalid("out of range"))?;
65                set(slot, n)
66            }
67
68            fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
69                slot.ok_or(ValueError::Missing)
70            }
71        }
72    )*};
73}
74
75unsigned!(u8, u16, u32, u64, u128, usize);
76signed!(i8, i16, i32, i64, i128, isize);
77
78macro_rules! float {
79    ($($t:ty),*) => {$(
80        impl FromField for $t {
81            type Slot = Option<$t>;
82
83            fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
84                let n = value.parse::<$t>().map_err(|_| ValueError::Invalid("not a number"))?;
85                if !n.is_finite() {
86                    return Err(ValueError::Invalid("not a finite number"));
87                }
88                set(slot, n)
89            }
90
91            fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
92                slot.ok_or(ValueError::Missing)
93            }
94        }
95    )*};
96}
97
98float!(f32, f64);
99
100impl FromField for bool {
101    type Slot = Option<bool>;
102
103    fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
104        let b = match value {
105            "on" | "true" | "1" => true,
106            "off" | "false" | "0" => false,
107            _ => return Err(ValueError::Invalid("not on, true, 1, off, false, or 0")),
108        };
109        set(slot, b)
110    }
111
112    /// An unchecked checkbox sends nothing: missing is `false`.
113    fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
114        Ok(slot.unwrap_or(false))
115    }
116}
117
118impl FromField for char {
119    type Slot = Option<char>;
120
121    fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
122        let mut chars = value.chars();
123        match (chars.next(), chars.next()) {
124            (Some(c), None) => set(slot, c),
125            _ => Err(ValueError::Invalid("not exactly one character")),
126        }
127    }
128
129    fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
130        slot.ok_or(ValueError::Missing)
131    }
132}
133
134impl<T: FromField> FromField for Option<T> {
135    type Slot = (bool, T::Slot);
136
137    fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
138        slot.0 = true;
139        T::accept_text(&mut slot.1, value)
140    }
141
142    fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
143        slot.0 = true;
144        T::accept_file(&mut slot.1, file)
145    }
146
147    fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
148        if !slot.0 {
149            return Ok(None);
150        }
151        match T::finish(slot.1) {
152            Ok(v) => Ok(Some(v)),
153            // A file input left empty arrives but holds nothing.
154            Err(ValueError::Missing) => Ok(None),
155            Err(e) => Err(e),
156        }
157    }
158}
159
160impl<T: FromField> FromField for Vec<T> {
161    type Slot = Vec<T>;
162
163    fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
164        let mut one = T::Slot::default();
165        T::accept_text(&mut one, value)?;
166        slot.push(T::finish(one)?);
167        Ok(())
168    }
169
170    fn accept_file(slot: &mut Self::Slot, file: FileValue<'_>) -> Result<(), ValueError> {
171        let mut one = T::Slot::default();
172        T::accept_file(&mut one, file)?;
173        match T::finish(one) {
174            Ok(v) => slot.push(v),
175            Err(ValueError::Missing) => {}
176            Err(e) => return Err(e),
177        }
178        Ok(())
179    }
180
181    fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
182        Ok(slot)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    fn one<T: FromField>(values: &[&str]) -> Result<T, ValueError> {
191        let mut slot = T::Slot::default();
192        for v in values {
193            T::accept_text(&mut slot, v)?;
194        }
195        T::finish(slot)
196    }
197
198    #[test]
199    fn test_integers_strict() {
200        assert_eq!(one::<u8>(&["255"]), Ok(255));
201        assert_eq!(one::<u8>(&["007"]), Ok(7));
202        assert_eq!(
203            one::<u8>(&["256"]),
204            Err(ValueError::Invalid("out of range"))
205        );
206        for bad in ["+1", " 1", "1 ", "", "-0", "1.0", "0x1"] {
207            assert!(one::<u8>(&[bad]).is_err(), "{bad:?}");
208        }
209        assert_eq!(one::<i8>(&["-128"]), Ok(-128));
210        assert_eq!(
211            one::<i8>(&["-129"]),
212            Err(ValueError::Invalid("out of range"))
213        );
214        assert!(
215            one::<i8>(&["+1"]).is_err()
216                && one::<i8>(&["-"]).is_err()
217                && one::<i8>(&["--1"]).is_err()
218        );
219        assert_eq!(one::<u64>(&[]), Err(ValueError::Missing));
220        assert_eq!(one::<u64>(&["1", "2"]), Err(ValueError::Duplicate));
221        assert_eq!(
222            one::<u128>(&["340282366920938463463374607431768211455"]),
223            Ok(u128::MAX)
224        );
225    }
226
227    #[test]
228    fn test_floats_refuse_non_finite() {
229        assert_eq!(one::<f64>(&["1e308"]), Ok(1e308));
230        assert_eq!(one::<f64>(&["-0"]), Ok(-0.0));
231        for bad in ["1e309", "NaN", "inf", "-inf", "infinity", "", "abc"] {
232            assert!(one::<f64>(&[bad]).is_err(), "{bad:?}");
233        }
234    }
235
236    #[test]
237    fn test_bool_spellings_and_missing() {
238        for (s, v) in [
239            ("on", true),
240            ("true", true),
241            ("1", true),
242            ("off", false),
243            ("false", false),
244            ("0", false),
245        ] {
246            assert_eq!(one::<bool>(&[s]), Ok(v));
247        }
248        assert!(one::<bool>(&["ON"]).is_err() && one::<bool>(&["yes"]).is_err());
249        assert_eq!(one::<bool>(&[]), Ok(false));
250        assert_eq!(one::<bool>(&["on", "on"]), Err(ValueError::Duplicate));
251    }
252
253    #[test]
254    fn test_char_option_vec() {
255        assert_eq!(one::<char>(&["é"]), Ok('é'));
256        assert!(one::<char>(&[""]).is_err() && one::<char>(&["ab"]).is_err());
257        assert_eq!(one::<Option<u8>>(&[]), Ok(None));
258        assert_eq!(one::<Option<u8>>(&["3"]), Ok(Some(3)));
259        assert_eq!(one::<Option<u8>>(&["3", "4"]), Err(ValueError::Duplicate));
260        assert_eq!(one::<Vec<u8>>(&[]), Ok(Vec::new()));
261        assert_eq!(one::<Vec<u8>>(&["1", "2", "3"]), Ok(alloc::vec![1, 2, 3]));
262        assert!(one::<Vec<u8>>(&["1", "x"]).is_err());
263        assert_eq!(one::<String>(&["a"]), Ok(String::from("a")));
264    }
265
266    #[test]
267    fn test_text_into_file_field_is_wrong_kind() {
268        let mut slot = <super::super::FileData as FromField>::Slot::default();
269        assert_eq!(
270            <super::super::FileData as FromField>::accept_text(&mut slot, "x"),
271            Err(ValueError::ExpectedFile)
272        );
273    }
274}