noggin_parser/
from_header_value.rs

1/// The `FromHeaderValue` trait provides a mechanism for parsing individual
2/// HTTP header values from string slices.
3///
4/// Implementers of this trait can convert a raw string representation of an
5/// HTTP header value into a more strongly typed representation. This trait
6/// abstracts the parsing logic for specific header value types.
7///
8/// This trait is similar in spirit to the standard library's `FromStr` trait,
9/// but specifically tailored for HTTP header values and capable of supporting
10/// zero-copy parsing.
11pub trait FromHeaderValue<'de>: Sized {
12    /// Parses an HTTP header value from the provided string slice.
13    ///
14    /// # Parameters
15    ///
16    /// * `slice`: A string slice representing the raw value of an HTTP header.
17    ///
18    /// # Returns
19    ///
20    /// * `Option<Self>`: Returns the parsed header value if parsing is successful,
21    ///   or `None` if parsing fails.
22    fn parse_header_value(slice: &'de str) -> Option<Self>;
23}
24
25fn trim(string: &str) -> &str {
26    string.trim_matches(' ').trim_end_matches(' ')
27}
28
29impl<'de> FromHeaderValue<'de> for bool {
30    fn parse_header_value(slice: &'de str) -> Option<Self> {
31        match trim(slice) {
32            "true" => Some(true),
33            "false" => Some(false),
34            _ => None,
35        }
36    }
37}
38
39impl<'de> FromHeaderValue<'de> for u8 {
40    fn parse_header_value(slice: &'de str) -> Option<Self> {
41        trim(slice).parse().ok()
42    }
43}
44
45impl<'de> FromHeaderValue<'de> for u16 {
46    fn parse_header_value(slice: &'de str) -> Option<Self> {
47        trim(slice).parse().ok()
48    }
49}
50
51impl<'de> FromHeaderValue<'de> for u32 {
52    fn parse_header_value(slice: &'de str) -> Option<Self> {
53        trim(slice).parse().ok()
54    }
55}
56
57impl<'de> FromHeaderValue<'de> for u64 {
58    fn parse_header_value(slice: &'de str) -> Option<Self> {
59        trim(slice).parse().ok()
60    }
61}
62
63impl<'de> FromHeaderValue<'de> for u128 {
64    fn parse_header_value(slice: &'de str) -> Option<Self> {
65        trim(slice).parse().ok()
66    }
67}
68
69impl<'de> FromHeaderValue<'de> for usize {
70    fn parse_header_value(slice: &'de str) -> Option<Self> {
71        trim(slice).parse().ok()
72    }
73}
74
75impl<'de> FromHeaderValue<'de> for i8 {
76    fn parse_header_value(slice: &'de str) -> Option<Self> {
77        trim(slice).parse().ok()
78    }
79}
80
81impl<'de> FromHeaderValue<'de> for i16 {
82    fn parse_header_value(slice: &'de str) -> Option<Self> {
83        trim(slice).parse().ok()
84    }
85}
86
87impl<'de> FromHeaderValue<'de> for i32 {
88    fn parse_header_value(slice: &'de str) -> Option<Self> {
89        trim(slice).parse().ok()
90    }
91}
92
93impl<'de> FromHeaderValue<'de> for i64 {
94    fn parse_header_value(slice: &'de str) -> Option<Self> {
95        trim(slice).parse().ok()
96    }
97}
98
99impl<'de> FromHeaderValue<'de> for i128 {
100    fn parse_header_value(slice: &'de str) -> Option<Self> {
101        trim(slice).parse().ok()
102    }
103}
104
105impl<'de> FromHeaderValue<'de> for isize {
106    fn parse_header_value(slice: &'de str) -> Option<Self> {
107        trim(slice).parse().ok()
108    }
109}
110
111impl<'de> FromHeaderValue<'de> for f32 {
112    fn parse_header_value(slice: &'de str) -> Option<Self> {
113        trim(slice).parse().ok()
114    }
115}
116
117impl<'de> FromHeaderValue<'de> for f64 {
118    fn parse_header_value(slice: &'de str) -> Option<Self> {
119        trim(slice).parse().ok()
120    }
121}
122
123impl<'de> FromHeaderValue<'de> for &'de [u8] {
124    fn parse_header_value(slice: &'de str) -> Option<Self> {
125        Some(slice.as_bytes())
126    }
127}
128
129impl<'de> FromHeaderValue<'de> for &'de str {
130    fn parse_header_value(slice: &'de str) -> Option<Self> {
131        Some(trim(slice))
132    }
133}
134
135impl<'de> FromHeaderValue<'de> for String {
136    fn parse_header_value(slice: &'de str) -> Option<Self> {
137        Some(trim(slice).to_owned())
138    }
139}
140
141impl<'de, T: FromHeaderValue<'de>> FromHeaderValue<'de> for Vec<T> {
142    fn parse_header_value(slice: &'de str) -> Option<Self> {
143        let mut values = vec![];
144        for value in slice.split(',') {
145            let parsed = T::parse_header_value(value)?;
146            values.push(parsed);
147        }
148        Some(values)
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use rstest::rstest;
156    use std::usize;
157
158    #[rstest]
159    #[case("true", Some(true))]
160    #[case("false", Some(false))]
161    #[case("falsey", None)]
162    fn bool_test(#[case] input: &str, #[case] expected: Option<bool>) {
163        assert_eq!(expected, bool::parse_header_value(input));
164    }
165
166    #[rstest]
167    #[case("42", Some(42))]
168    #[case("255", Some(255))]
169    #[case("-1", None)]
170    #[case("256", None)]
171    fn u8_test(#[case] input: &str, #[case] expected: Option<u8>) {
172        assert_eq!(expected, u8::parse_header_value(input));
173    }
174
175    #[rstest]
176    #[case("42", Some(42))]
177    #[case("-1", None)]
178    fn u16_test(#[case] input: &str, #[case] expected: Option<u16>) {
179        assert_eq!(expected, u16::parse_header_value(input));
180    }
181
182    #[rstest]
183    #[case("42", Some(42))]
184    #[case("-1", None)]
185    fn u32_test(#[case] input: &str, #[case] expected: Option<u32>) {
186        assert_eq!(expected, u32::parse_header_value(input));
187    }
188
189    #[rstest]
190    #[case("42", Some(42))]
191    #[case("-1", None)]
192    fn u64_test(#[case] input: &str, #[case] expected: Option<u64>) {
193        assert_eq!(expected, u64::parse_header_value(input));
194    }
195
196    #[rstest]
197    #[case("42", Some(42))]
198    #[case("-1", None)]
199    fn u128_test(#[case] input: &str, #[case] expected: Option<u128>) {
200        assert_eq!(expected, u128::parse_header_value(input));
201    }
202
203    #[rstest]
204    #[case("42", Some(42))]
205    #[case("-1", None)]
206    fn usize_test(#[case] input: &str, #[case] expected: Option<usize>) {
207        assert_eq!(expected, usize::parse_header_value(input));
208    }
209
210    #[rstest]
211    #[case("42", Some(42))]
212    #[case("-42", Some(-42))]
213    #[case("idk", None)]
214    fn i8_test(#[case] input: &str, #[case] expected: Option<i8>) {
215        assert_eq!(expected, i8::parse_header_value(input));
216    }
217
218    #[rstest]
219    #[case("42", Some(42))]
220    #[case("-42", Some(-42))]
221    #[case("idk", None)]
222    fn i16_test(#[case] input: &str, #[case] expected: Option<i16>) {
223        assert_eq!(expected, i16::parse_header_value(input));
224    }
225
226    #[rstest]
227    #[case("42", Some(42))]
228    #[case("-42", Some(-42))]
229    #[case("idk", None)]
230    fn i32_test(#[case] input: &str, #[case] expected: Option<i32>) {
231        assert_eq!(expected, i32::parse_header_value(input));
232    }
233
234    #[rstest]
235    #[case("42", Some(42))]
236    #[case("-42", Some(-42))]
237    #[case("idk", None)]
238    fn i64_test(#[case] input: &str, #[case] expected: Option<i64>) {
239        assert_eq!(expected, i64::parse_header_value(input));
240    }
241
242    #[rstest]
243    #[case("42", Some(42))]
244    #[case("-42", Some(-42))]
245    #[case("idk", None)]
246    fn i128_test(#[case] input: &str, #[case] expected: Option<i128>) {
247        assert_eq!(expected, i128::parse_header_value(input));
248    }
249
250    #[rstest]
251    #[case("42", Some(42))]
252    #[case("-42", Some(-42))]
253    #[case("idk", None)]
254    fn isize_test(#[case] input: &str, #[case] expected: Option<isize>) {
255        assert_eq!(expected, isize::parse_header_value(input));
256    }
257
258    #[rstest]
259    #[case("42.7", Some(42.7))]
260    #[case("-42.7", Some(-42.7))]
261    #[case("idk", None)]
262    fn f32_test(#[case] input: &str, #[case] expected: Option<f32>) {
263        assert_eq!(expected, f32::parse_header_value(input));
264    }
265
266    #[rstest]
267    #[case("5.6789", Some(5.6789f64))]
268    #[case("-5.6789", Some(-5.6789f64))]
269    #[case("idk", None)]
270    fn f64_test(#[case] input: &str, #[case] expected: Option<f64>) {
271        assert_eq!(expected, f64::parse_header_value(input));
272    }
273
274    #[rstest]
275    #[case("hello", Some(b"hello".as_slice()))]
276    #[case(" hello ", Some(b" hello ".as_slice()))]
277    fn bytes_test(#[case] input: &str, #[case] expected: Option<&[u8]>) {
278        assert_eq!(expected, <&[u8]>::parse_header_value(input));
279    }
280
281    #[rstest]
282    #[case("hello", Some("hello"))]
283    #[case(" hello ", Some("hello"))]
284    fn str_test(#[case] input: &str, #[case] expected: Option<&str>) {
285        assert_eq!(expected, <&str>::parse_header_value(input));
286    }
287
288    #[rstest]
289    #[case("hello", Some("hello".to_owned()))]
290    #[case(" hello ", Some("hello".to_owned()))]
291    fn string_test(#[case] input: &str, #[case] expected: Option<String>) {
292        assert_eq!(expected, String::parse_header_value(input));
293    }
294
295    #[rstest]
296    #[case("1", Some(vec![1]))]
297    #[case("1, 2", Some(vec![1, 2]))]
298    #[case("1, 2, 3", Some(vec![1, 2, 3]))]
299    #[case("idk", None)]
300    fn vec_test(#[case] input: &str, #[case] expected: Option<Vec<u8>>) {
301        assert_eq!(expected, Vec::<_>::parse_header_value(input));
302    }
303}