volga/http/endpoints/args/
path.rs

1//! Extractors for route/path segments
2
3use crate::{HttpRequest, error::Error};
4use futures_util::future::{ready, ok, Ready};
5use hyper::http::{request::Parts, Extensions};
6use serde::de::DeserializeOwned;
7
8use std::{
9    net::{IpAddr, SocketAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
10    ffi::{CString, OsString},
11    path::PathBuf,
12    num::NonZero,
13    borrow::Cow,
14    fmt::{self, Display, Formatter},
15    ops::{Deref, DerefMut}
16};
17
18use crate::http::endpoints::{
19    route::{PathArg, PathArgs},
20    args::{
21        FromPayload, 
22        FromRequestParts, 
23        FromRequestRef, 
24        Payload, Source
25    }
26};
27
28/// Wraps typed data extracted from path args
29/// 
30/// # Example
31/// ```no_run
32/// use volga::{HttpResult, Path, ok};
33/// use serde::Deserialize;
34/// 
35/// #[derive(Deserialize)]
36/// struct Params {
37///     name: String,
38/// }
39/// 
40/// async fn handle(params: Path<Params>) -> HttpResult {
41///     ok!("Hello {}", params.name)
42/// }
43/// ```
44#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
45pub struct Path<T>(pub T);
46
47impl<T> Path<T> {
48    /// Unwraps the inner `T`
49    #[inline]
50    pub fn into_inner(self) -> T {
51        self.0
52    }
53}
54
55impl<T> Deref for Path<T> {
56    type Target = T;
57
58    #[inline]
59    fn deref(&self) -> &T {
60        &self.0
61    }
62}
63
64impl<T> DerefMut for Path<T> {
65    #[inline]
66    fn deref_mut(&mut self) -> &mut T {
67        &mut self.0
68    }
69}
70
71impl<T: Display> Display for Path<T> {
72    #[inline]
73    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
74        self.0.fmt(f)
75    }
76}
77
78impl<T: DeserializeOwned> Path<T> {
79    /// Parses the slice of tuples `(String, String)` into [`Path<T>`]
80    #[inline]
81    pub(crate) fn from_slice(route_params: &PathArgs) -> Result<Self, Error> {
82        let route_str = PathArg::make_query_str(route_params)?;
83        serde_urlencoded::from_str::<T>(&route_str)
84            .map(Path)
85            .map_err(PathError::from_serde_error)
86    }
87}
88
89impl<T: DeserializeOwned + Send> TryFrom<&Extensions> for Path<T> {
90    type Error = Error;
91    
92    #[inline]
93    fn try_from(extensions: &Extensions) -> Result<Self, Error> {
94        extensions
95            .get::<PathArgs>()
96            .ok_or_else(PathError::args_missing)
97            .and_then(|params| Self::from_slice(params))
98    }
99}
100
101impl<T: DeserializeOwned + Send> TryFrom<&Parts> for Path<T> {
102    type Error = Error;
103
104    #[inline]
105    fn try_from(parts: &Parts) -> Result<Self, Error> {
106        let ext = &parts.extensions;
107        ext.try_into()
108    }
109}
110
111/// Extracts path args from request parts into `Path<T>`
112/// where T is deserializable `struct`
113impl<T: DeserializeOwned + Send> FromRequestParts for Path<T> {
114    #[inline]
115    fn from_parts(parts: &Parts) -> Result<Self, Error> {
116        parts.try_into()
117    }
118}
119
120/// Extracts path args from request into `Path<T>`
121/// where T is deserializable `struct`
122impl<T: DeserializeOwned + Send> FromRequestRef for Path<T> {
123    #[inline]
124    fn from_request(req: &HttpRequest) -> Result<Self, Error> {
125        req.extensions().try_into()
126    }
127}
128
129/// Extracts path args from request parts into `Path<T>`
130/// where T is deserializable `struct`
131impl<T: DeserializeOwned + Send> FromPayload for Path<T> {
132    type Future = Ready<Result<Self, Error>>;
133
134    #[inline]
135    fn from_payload(payload: Payload<'_>) -> Self::Future {
136        let Payload::PathArgs(params) = payload else { unreachable!() };
137        ready(Self::from_slice(&params))
138    }
139
140    #[inline]
141    fn source() -> Source {
142        Source::PathArgs
143    }
144}
145
146impl FromPayload for String {
147    type Future = Ready<Result<Self, Error>>;
148    
149    #[inline]
150    fn from_payload(payload: Payload<'_>) -> Self::Future {
151        let Payload::Path(param) = payload else { unreachable!() };
152        ok(param.value.into_string())
153    }
154    
155    #[inline]
156    fn source() -> Source {
157        Source::Path
158    }
159}
160
161impl FromPayload for Cow<'static, str> {
162    type Future = Ready<Result<Self, Error>>;
163
164    #[inline]
165    fn from_payload(payload: Payload<'_>) -> Self::Future {
166        let Payload::Path(param) = payload else { unreachable!() };
167        ok(Cow::Owned(param.value.into_string()))
168    }
169
170    #[inline]
171    fn source() -> Source {
172        Source::Path
173    }
174}
175
176impl FromPayload for Box<str> {
177    type Future = Ready<Result<Self, Error>>;
178
179    #[inline]
180    fn from_payload(payload: Payload<'_>) -> Self::Future {
181        let Payload::Path(param) = payload else { unreachable!() };
182        ok(param.value)
183    }
184
185    #[inline]
186    fn source() -> Source {
187        Source::Path
188    }
189}
190
191impl FromPayload for Box<[u8]> {
192    type Future = Ready<Result<Self, Error>>;
193
194    #[inline]
195    fn from_payload(payload: Payload<'_>) -> Self::Future {
196        let Payload::Path(param) = payload else { unreachable!() };
197        ok(param.value.into_boxed_bytes())
198    }
199
200    #[inline]
201    fn source() -> Source {
202        Source::Path
203    }
204}
205
206macro_rules! impl_from_payload {
207    { $($type:ty),* $(,)? } => {
208        $(impl FromPayload for $type {
209            type Future = Ready<Result<Self, Error>>;
210            #[inline]
211            fn from_payload(payload: Payload<'_>) -> Self::Future {
212                let Payload::Path(param) = payload else { unreachable!() };
213                ready(param.value
214                    .parse::<$type>()
215                    .map_err(|_| PathError::type_mismatch(param.name.as_ref())))
216            }
217            #[inline]
218            fn source() -> Source {
219                Source::Path
220            }
221        })*
222    };
223}
224
225impl_from_payload! {
226    bool, 
227    char,
228    i8, i16, i32, i64, i128, isize,
229    u8, u16, u32, u64, u128, usize,
230    f32, f64,
231    NonZero<i8>, NonZero<i16>, NonZero<i32>, NonZero<i64>, NonZero<i128>, NonZero<isize>,
232    NonZero<u8>, NonZero<u16>, NonZero<u32>, NonZero<u64>, NonZero<u128>, NonZero<usize>,
233    IpAddr, SocketAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6,
234    CString, OsString,
235    PathBuf
236}
237
238/// Describes errors of path extractor
239struct PathError;
240
241impl PathError {
242    #[inline]
243    fn from_serde_error(err: serde::de::value::Error) -> Error {
244        Error::client_error(format!("Path parsing error: {err}"))
245    }
246
247    #[inline]
248    fn type_mismatch(arg: &str) -> Error {
249        Error::client_error(format!("Path parsing error: argument `{arg}` type mismatch"))
250    }
251
252    #[inline]
253    fn args_missing() -> Error {
254        Error::client_error("Path parsing error: missing arguments")
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use hyper::{Request, http::Extensions};
261    use serde::Deserialize;
262    use crate::{HttpBody, HttpRequest, Path};
263    use crate::http::endpoints::route::{PathArg, PathArgs};
264    use crate::http::endpoints::args::{FromPayload, FromRequestParts, FromRequestRef, Payload};
265
266    #[derive(Deserialize)]
267    struct Params {
268        id: u32,
269        name: String
270    }
271
272    #[tokio::test]
273    async fn it_reads_isize_from_payload() {
274        let param = PathArg { name: "id".into(), value: "123".into() };
275        let id = isize::from_payload(Payload::Path(param)).await.unwrap();
276
277        assert_eq!(id, 123);
278    }
279    
280    #[tokio::test]
281    async fn it_reads_i8_from_payload() {
282        let param = PathArg { name: "id".into(), value: "123".into() };
283        let id = i8::from_payload(Payload::Path(param)).await.unwrap();
284
285        assert_eq!(id, 123);
286    }
287    
288    #[tokio::test]
289    async fn it_reads_i16_from_payload() {
290        let param = PathArg { name: "id".into(), value: "123".into() };
291        let id = i16::from_payload(Payload::Path(param)).await.unwrap();
292
293        assert_eq!(id, 123);
294    }
295    
296    #[tokio::test]
297    async fn it_reads_i32_from_payload() {
298        let param = PathArg { name: "id".into(), value: "123".into() };
299        let id = i32::from_payload(Payload::Path(param)).await.unwrap();
300        
301        assert_eq!(id, 123);
302    }
303
304    #[tokio::test]
305    async fn it_reads_i64_from_payload() {
306        let param = PathArg { name: "id".into(), value: "123".into() };
307        let id = i64::from_payload(Payload::Path(param)).await.unwrap();
308
309        assert_eq!(id, 123);
310    }
311
312    #[tokio::test]
313    async fn it_reads_i128_from_payload() {
314        let param = PathArg { name: "id".into(), value: "123".into() };
315        let id = i128::from_payload(Payload::Path(param)).await.unwrap();
316
317        assert_eq!(id, 123);
318    }
319
320    #[tokio::test]
321    async fn it_reads_usize_from_payload() {
322        let param = PathArg { name: "id".into(), value: "123".into() };
323        let id = usize::from_payload(Payload::Path(param)).await.unwrap();
324
325        assert_eq!(id, 123);
326    }
327
328    #[tokio::test]
329    async fn it_reads_u8_from_payload() {
330        let param = PathArg { name: "id".into(), value: "123".into() };
331        let id = u8::from_payload(Payload::Path(param)).await.unwrap();
332
333        assert_eq!(id, 123);
334    }
335
336    #[tokio::test]
337    async fn it_reads_u16_from_payload() {
338        let param = PathArg { name: "id".into(), value: "123".into() };
339        let id = u16::from_payload(Payload::Path(param)).await.unwrap();
340
341        assert_eq!(id, 123);
342    }
343
344    #[tokio::test]
345    async fn it_reads_u32_from_payload() {
346        let param = PathArg { name: "id".into(), value: "123".into() };
347        let id = u32::from_payload(Payload::Path(param)).await.unwrap();
348
349        assert_eq!(id, 123);
350    }
351
352    #[tokio::test]
353    async fn it_reads_u128_from_payload() {
354        let param = PathArg { name: "id".into(), value: "123".into() };
355        let id = u128::from_payload(Payload::Path(param)).await.unwrap();
356
357        assert_eq!(id, 123);
358    }
359
360    #[tokio::test]
361    async fn it_reads_string_from_payload() {
362        let param = PathArg { name: "id".into(), value: "123".into() };
363        let id = String::from_payload(Payload::Path(param)).await.unwrap();
364
365        assert_eq!(id, "123");
366    }
367
368    #[tokio::test]
369    async fn it_reads_box_str_from_payload() {
370        let param = PathArg { name: "id".into(), value: "123".into() };
371        let id = Box::<str>::from_payload(Payload::Path(param)).await.unwrap();
372
373        assert_eq!(&*id, "123");
374    }
375
376    #[tokio::test]
377    async fn it_reads_box_bytes_from_payload() {
378        let param = PathArg { name: "id".into(), value: "123".into() };
379        let id = Box::<[u8]>::from_payload(Payload::Path(param)).await.unwrap();
380
381        assert_eq!(&*id, [b'1', b'2', b'3']);
382    }
383
384    #[tokio::test]
385    async fn it_reads_path_from_payload() {
386        let args: PathArgs = smallvec::smallvec![
387            PathArg { name: "id".into(), value: "123".into() },
388            PathArg { name: "name".into(), value: "John".into() }
389        ];
390
391        let path = Path::<Params>::from_payload(Payload::PathArgs(args)).await.unwrap();
392
393        assert_eq!(path.id, 123u32);
394        assert_eq!(path.name, "John")
395    }
396    
397    #[test]
398    fn it_parses_slice() {
399        let args: PathArgs = smallvec::smallvec![
400            PathArg { name: "id".into(), value: "123".into() },
401            PathArg { name: "name".into(), value: "John".into() }
402        ];
403        
404        let path = Path::<Params>::from_slice(&args).unwrap();
405        
406        assert_eq!(path.id, 123u32);
407        assert_eq!(path.name, "John")
408    }
409
410    #[test]
411    fn it_parses_request_extensions() {
412        let args: PathArgs = smallvec::smallvec![
413            PathArg { name: "id".into(), value: "123".into() },
414            PathArg { name: "name".into(), value: "John".into() }
415        ];
416        
417        let mut ext = Extensions::new();
418        ext.insert(args);
419
420        let path = Path::<Params>::try_from(&ext).unwrap();
421
422        assert_eq!(path.id, 123u32);
423        assert_eq!(path.name, "John")
424    }
425
426    #[tokio::test]
427    async fn it_reads_path_from_parts() {
428        let args: PathArgs = smallvec::smallvec![
429            PathArg { name: "id".into(), value: "123".into() },
430            PathArg { name: "name".into(), value: "John".into() }
431        ];
432
433        let req = Request::get("/")
434            .extension(args)
435            .body(())
436            .unwrap();
437
438        let (parts, _) = req.into_parts();
439        let path = Path::<Params>::from_parts(&parts).unwrap();
440
441        assert_eq!(path.id, 123u32);
442        assert_eq!(path.name, "John")
443    }
444
445    #[tokio::test]
446    async fn it_reads_path_from_request_ref() {
447        let args: PathArgs = smallvec::smallvec![
448            PathArg { name: "id".into(), value: "123".into() },
449            PathArg { name: "name".into(), value: "John".into() }
450        ];
451
452        let req = Request::get("/")
453            .extension(args)
454            .body(HttpBody::empty())
455            .unwrap();
456
457        let (parts, body) = req.into_parts();
458        let req = HttpRequest::from_parts(parts, body);
459        let path = Path::<Params>::from_request(&req).unwrap();
460
461        assert_eq!(path.id, 123u32);
462        assert_eq!(path.name, "John")
463    }
464}