front_line_router/
from_route.rs

1/// A trait to enable zero-copy parsing from route paths.
2///
3/// This trait is designed as an alternative to `FromStr` to support both zero-copy and copy parsing.
4/// It's especially useful for HTTP route parsing where parts of the route can be efficiently parsed
5/// without the need to allocate memory for every segment of the route. This can lead to performance
6/// benefits, especially in web applications where route parsing happens frequently.
7///
8/// Implement this trait for types that need to be parsed from route paths.
9///
10/// # Examples
11///
12/// ```
13/// // Suppose you have a route segment "/user/42"
14/// // and you want to directly parse "42" into a UserId type.
15///
16/// use front_line_router::FromRoute;
17///
18/// struct UserId(u32);
19///
20/// impl<'de> FromRoute<'de> for UserId {
21///     fn parse_path_variable(slice: &'de str) -> Option<Self> {
22///         slice.parse().map(UserId).ok()
23///     }
24/// }
25/// ```
26///
27/// Zero-copy example:
28///
29/// ```
30/// // For cases where the exact slice from the route path can be directly used,
31/// // the `FromRoute` trait allows efficient zero-copy parsing.
32///
33/// use front_line_router::FromRoute;
34///
35/// struct UserName<'a>(&'a str);
36///
37/// impl<'de> FromRoute<'de> for UserName<'de> {
38///     fn parse_path_variable(slice: &'de str) -> Option<Self> {
39///         if slice.is_empty() {
40///             None
41///         } else {
42///             Some(UserName(slice))
43///         }
44///     }
45/// }
46///
47/// // Given a route segment "/user/alice", "alice" can be parsed directly as UserName
48/// // without creating a new String.
49/// ```
50pub trait FromRoute<'de>: Sized {
51    /// Parses a value from a route segment.
52    ///
53    /// # Arguments
54    ///
55    /// * `slice` - A segment of a route, typically a part between slashes in a URL.
56    ///
57    /// # Returns
58    ///
59    /// Returns `Some(T)` if the segment can be successfully parsed into type `T`. Otherwise,
60    /// returns `None`.
61    fn parse_path_variable(slice: &'de str) -> Option<Self>;
62}
63
64impl<'de> FromRoute<'de> for bool {
65    fn parse_path_variable(slice: &'de str) -> Option<Self> {
66        match slice {
67            "true" => Some(true),
68            "false" => Some(false),
69            _ => None,
70        }
71    }
72}
73
74impl<'de> FromRoute<'de> for u8 {
75    fn parse_path_variable(slice: &'de str) -> Option<Self> {
76        slice.parse().ok()
77    }
78}
79
80impl<'de> FromRoute<'de> for u16 {
81    fn parse_path_variable(slice: &'de str) -> Option<Self> {
82        slice.parse().ok()
83    }
84}
85
86impl<'de> FromRoute<'de> for u32 {
87    fn parse_path_variable(slice: &'de str) -> Option<Self> {
88        slice.parse().ok()
89    }
90}
91
92impl<'de> FromRoute<'de> for u64 {
93    fn parse_path_variable(slice: &'de str) -> Option<Self> {
94        slice.parse().ok()
95    }
96}
97
98impl<'de> FromRoute<'de> for u128 {
99    fn parse_path_variable(slice: &'de str) -> Option<Self> {
100        slice.parse().ok()
101    }
102}
103
104impl<'de> FromRoute<'de> for usize {
105    fn parse_path_variable(slice: &'de str) -> Option<Self> {
106        slice.parse().ok()
107    }
108}
109
110impl<'de> FromRoute<'de> for i8 {
111    fn parse_path_variable(slice: &'de str) -> Option<Self> {
112        slice.parse().ok()
113    }
114}
115
116impl<'de> FromRoute<'de> for i16 {
117    fn parse_path_variable(slice: &'de str) -> Option<Self> {
118        slice.parse().ok()
119    }
120}
121
122impl<'de> FromRoute<'de> for i32 {
123    fn parse_path_variable(slice: &'de str) -> Option<Self> {
124        slice.parse().ok()
125    }
126}
127
128impl<'de> FromRoute<'de> for i64 {
129    fn parse_path_variable(slice: &'de str) -> Option<Self> {
130        slice.parse().ok()
131    }
132}
133
134impl<'de> FromRoute<'de> for i128 {
135    fn parse_path_variable(slice: &'de str) -> Option<Self> {
136        slice.parse().ok()
137    }
138}
139
140impl<'de> FromRoute<'de> for isize {
141    fn parse_path_variable(slice: &'de str) -> Option<Self> {
142        slice.parse().ok()
143    }
144}
145
146impl<'de> FromRoute<'de> for f32 {
147    fn parse_path_variable(slice: &'de str) -> Option<Self> {
148        slice.parse().ok()
149    }
150}
151
152impl<'de> FromRoute<'de> for f64 {
153    fn parse_path_variable(slice: &'de str) -> Option<Self> {
154        slice.parse().ok()
155    }
156}
157
158impl<'de> FromRoute<'de> for &'de [u8] {
159    fn parse_path_variable(slice: &'de str) -> Option<Self> {
160        Some(slice.as_bytes())
161    }
162}
163
164impl<'de> FromRoute<'de> for &'de str {
165    fn parse_path_variable(slice: &'de str) -> Option<Self> {
166        Some(slice)
167    }
168}
169
170impl<'de> FromRoute<'de> for String {
171    fn parse_path_variable(slice: &'de str) -> Option<Self> {
172        Some(slice.to_owned())
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::FromRoute;
179    use rstest::rstest;
180
181    #[rstest]
182    #[case("true", Some(true))]
183    #[case("false", Some(false))]
184    #[case("other", None)]
185    fn test_bool(#[case] input: &str, #[case] expected: Option<bool>) {
186        assert_eq!(bool::parse_path_variable(input), expected);
187    }
188
189    #[rstest]
190    #[case("42", Some(42))]
191    #[case("255", Some(255))]
192    #[case("-1", None)]
193    #[case("256", None)]
194    fn test_u8(#[case] input: &str, #[case] expected: Option<u8>) {
195        assert_eq!(u8::parse_path_variable(input), expected);
196    }
197
198    #[rstest]
199    #[case("42", Some(42))]
200    #[case("65535", Some(65535))]
201    #[case("-1", None)]
202    #[case("65536", None)]
203    fn test_u16(#[case] input: &str, #[case] expected: Option<u16>) {
204        assert_eq!(u16::parse_path_variable(input), expected);
205    }
206
207    #[rstest]
208    #[case("42", Some(42))]
209    #[case("4294967295", Some(4294967295))]
210    #[case("-1", None)]
211    #[case("4294967296", None)]
212    fn test_u32(#[case] input: &str, #[case] expected: Option<u32>) {
213        assert_eq!(u32::parse_path_variable(input), expected);
214    }
215
216    #[rstest]
217    #[case("42", Some(42))]
218    #[case("18446744073709551615", Some(18446744073709551615))]
219    #[case("-1", None)]
220    #[case("18446744073709551616", None)]
221    fn test_u64(#[case] input: &str, #[case] expected: Option<u64>) {
222        assert_eq!(u64::parse_path_variable(input), expected);
223    }
224
225    #[test]
226    fn test_usize() {
227        assert_eq!(usize::parse_path_variable("42"), Some(42_usize));
228    }
229
230    #[rstest]
231    #[case("42", Some(42))]
232    #[case(
233        "340282366920938463463374607431768211455",
234        Some(340282366920938463463374607431768211455)
235    )]
236    #[case("-1", None)]
237    #[case("340282366920938463463374607431768211456", None)]
238    fn test_u128(#[case] input: &str, #[case] expected: Option<u128>) {
239        assert_eq!(u128::parse_path_variable(input), expected);
240    }
241
242    #[rstest]
243    #[case("42", Some(42))]
244    #[case("-42", Some(-42))]
245    #[case("127", Some(127))]
246    #[case("-128", Some(-128))]
247    #[case("128", None)]
248    #[case("-129", None)]
249    fn test_i8(#[case] input: &str, #[case] expected: Option<i8>) {
250        assert_eq!(i8::parse_path_variable(input), expected);
251    }
252
253    #[rstest]
254    #[case("42", Some(42))]
255    #[case("-42", Some(-42))]
256    #[case("32767", Some(32767))]
257    #[case("-32768", Some(-32768))]
258    #[case("32768", None)]
259    #[case("-32769", None)]
260    fn test_i16(#[case] input: &str, #[case] expected: Option<i16>) {
261        assert_eq!(i16::parse_path_variable(input), expected);
262    }
263
264    #[rstest]
265    #[case("42", Some(42))]
266    #[case("-42", Some(-42))]
267    #[case("2147483647", Some(2147483647))]
268    #[case("-2147483648", Some(-2147483648))]
269    #[case("2147483648", None)]
270    #[case("-2147483649", None)]
271    fn test_i32(#[case] input: &str, #[case] expected: Option<i32>) {
272        assert_eq!(i32::parse_path_variable(input), expected);
273    }
274
275    #[rstest]
276    #[case("42", Some(42))]
277    #[case("-42", Some(-42))]
278    #[case("9223372036854775807", Some(9223372036854775807))]
279    #[case("-9223372036854775808", Some(-9223372036854775808))]
280    #[case("9223372036854775808", None)]
281    #[case("-9223372036854775809", None)]
282    fn test_i64(#[case] input: &str, #[case] expected: Option<i64>) {
283        assert_eq!(i64::parse_path_variable(input), expected);
284    }
285
286    #[rstest]
287    #[case("42", Some(42))]
288    #[case("-42", Some(-42))]
289    #[case(
290        "170141183460469231731687303715884105727",
291        Some(170141183460469231731687303715884105727)
292    )]
293    #[case("-170141183460469231731687303715884105728", Some(-170141183460469231731687303715884105728))]
294    #[case("170141183460469231731687303715884105728", None)]
295    #[case("-170141183460469231731687303715884105729", None)]
296    fn test_i128(#[case] input: &str, #[case] expected: Option<i128>) {
297        assert_eq!(i128::parse_path_variable(input), expected);
298    }
299
300    #[test]
301    fn test_isize() {
302        assert_eq!(isize::parse_path_variable("42"), Some(42_isize));
303        assert_eq!(isize::parse_path_variable("-42"), Some(-42_isize));
304    }
305
306    #[rstest]
307    #[case("5.5", Some(5.5f32))]
308    #[case("-5.5", Some(-5.5f32))]
309    #[case("not_a_float", None)]
310    fn test_f32(#[case] input: &str, #[case] expected: Option<f32>) {
311        assert_eq!(f32::parse_path_variable(input), expected);
312    }
313
314    #[rstest]
315    #[case("5.5", Some(5.5))]
316    #[case("-5.5", Some(-5.5))]
317    #[case("not_a_float", None)]
318    fn test_f64(#[case] input: &str, #[case] expected: Option<f64>) {
319        assert_eq!(f64::parse_path_variable(input), expected);
320    }
321
322    #[test]
323    fn test_bytes() {
324        assert_eq!(
325            <&[u8]>::parse_path_variable("test"),
326            Some(b"test".as_slice())
327        );
328    }
329
330    #[test]
331    fn test_str() {
332        assert_eq!(<&str>::parse_path_variable("test"), Some("test"));
333    }
334
335    #[test]
336    fn test_string() {
337        assert_eq!(String::parse_path_variable("test"), Some("test".to_owned()));
338    }
339}