Skip to main content

ntex/web/types/
path.rs

1//! Path extractor
2use std::{fmt, ops};
3
4use serde::de;
5
6use crate::web::error::PathError;
7use crate::web::{FromRequest, HttpRequest, State};
8use crate::{http::Payload, router::PathDeserializer};
9
10#[derive(PartialEq, Eq, PartialOrd, Ord)]
11/// Extract typed information from the request's path.
12///
13/// If the path segments cannot be deserialized into `T`, extraction fails with
14/// [`PathError`](crate::web::error::PathError), which the default error domain
15/// renders as `404 Not Found`.
16///
17/// Path segments are percent-decoded after the path is split on `/`, so
18/// extracted values can contain any character, including `/`. For example
19/// `/files/..%2F..%2Fetc` matches `/files/{name}` with `name` set to
20/// `../../etc`. Validate such values before using them, e.g. as a file system
21/// path.
22///
23/// ## Example
24///
25/// ```rust
26/// use ntex::web;
27///
28/// /// extract path info from "/{username}/{count}/index.html" url
29/// /// {username} - deserializes to a String
30/// /// {count} - deserializes to a u32
31/// async fn index(info: web::types::Path<(String, u32)>) -> String {
32///     format!("Welcome {}! {}", info.0, info.1)
33/// }
34///
35/// fn main() {
36///     let app = web::App::default().service(
37///         web::resource("/{username}/{count}/index.html") // <- define path parameters
38///              .route(web::get().to(index))               // <- register handler with `Path` extractor
39///     );
40/// }
41/// ```
42///
43/// It is possible to extract path information to a specific type that
44/// implements `Deserialize` trait from *serde*.
45///
46/// ```rust
47/// use std::convert::Infallible;
48/// use ntex::web;
49///
50/// #[derive(serde::Deserialize)]
51/// struct Info {
52///     username: String,
53/// }
54///
55/// /// extract `Info` from a path using serde
56/// async fn index(info: web::types::Path<Info>) -> Result<String, Infallible> {
57///     Ok(format!("Welcome {}!", info.username))
58/// }
59///
60/// fn main() {
61///     let app = web::App::default().service(
62///         web::resource("/{username}/index.html") // <- define path parameters
63///              .route(web::get().to(index)) // <- use handler with `Path` extractor
64///     );
65/// }
66/// ```
67pub struct Path<T> {
68    inner: T,
69}
70
71impl<T> Path<T> {
72    /// Deconstruct to an inner value
73    pub fn into_inner(self) -> T {
74        self.inner
75    }
76}
77
78impl<T> AsRef<T> for Path<T> {
79    fn as_ref(&self) -> &T {
80        &self.inner
81    }
82}
83
84impl<T> ops::Deref for Path<T> {
85    type Target = T;
86
87    fn deref(&self) -> &T {
88        &self.inner
89    }
90}
91
92impl<T> ops::DerefMut for Path<T> {
93    fn deref_mut(&mut self) -> &mut T {
94        &mut self.inner
95    }
96}
97
98impl<T> From<T> for Path<T> {
99    fn from(inner: T) -> Path<T> {
100        Path { inner }
101    }
102}
103
104impl<T: fmt::Debug> fmt::Debug for Path<T> {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        self.inner.fmt(f)
107    }
108}
109
110impl<T: fmt::Display> fmt::Display for Path<T> {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        self.inner.fmt(f)
113    }
114}
115
116/// Extract typed information from the request's path.
117///
118/// ## Example
119///
120/// ```rust
121/// use ntex::web;
122///
123/// /// extract path info from "/{username}/{count}/index.html" url
124/// /// {username} - deserializes to a String
125/// /// {count} - deserializes to a u32
126/// async fn index(info: web::types::Path<(String, u32)>) -> String {
127///     format!("Welcome {}! {}", info.0, info.1)
128/// }
129///
130/// fn main() {
131///     let app = web::App::default().service(
132///         web::resource("/{username}/{count}/index.html") // <- define path parameters
133///              .route(web::get().to(index)) // <- register handler with `Path` extractor
134///     );
135/// }
136/// ```
137///
138/// It is possible to extract path information to a specific type that
139/// implements `Deserialize` trait from *serde*.
140///
141/// ```rust
142/// use std::convert::Infallible;
143/// use ntex::web;
144///
145/// #[derive(serde::Deserialize)]
146/// struct Info {
147///     username: String,
148/// }
149///
150/// /// extract `Info` from a path using serde
151/// async fn index(info: web::types::Path<Info>) -> Result<String, Infallible> {
152///     Ok(format!("Welcome {}!", info.username))
153/// }
154///
155/// fn main() {
156///     let app = web::App::default().service(
157///         web::resource("/{username}/index.html") // <- define path parameters
158///              .route(web::get().to(index)) // <- use handler with `Path` extractor
159///     );
160/// }
161/// ```
162impl<T, St: State> FromRequest<St> for Path<T>
163where
164    T: de::DeserializeOwned,
165{
166    type Error = PathError;
167
168    async fn from_request(_: &St, req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
169        de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
170            .map(|inner| Path { inner })
171            .map_err(move |e| {
172                log::debug!(
173                    "Failed during Path extractor deserialization. \
174                     Request path: {:?}",
175                    req.path()
176                );
177                PathError::from(e)
178            })
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use crate::router::Router;
186    use crate::web::test::{TestRequest, from_request};
187
188    #[derive(thiserror::Error, serde::Deserialize, Debug)]
189    #[error("MyStruct({key}, {value})")]
190    struct MyStruct {
191        key: String,
192        value: String,
193    }
194
195    #[derive(serde::Deserialize)]
196    struct Test2 {
197        key: String,
198        value: u32,
199    }
200
201    #[crate::rt_test]
202    async fn test_extract_path_single() {
203        let mut router = Router::<usize>::builder();
204        router.path("/{value}/", 10).set_id(0);
205        let router = router.build();
206
207        let mut req = TestRequest::with_uri("/32/").to_srv_request();
208        router.recognize(req.match_info_mut());
209
210        let (req, mut pl, ()) = req.into_parts();
211        assert_eq!(
212            *from_request::<_, Path<i8>>(&(), &req, &mut pl)
213                .await
214                .unwrap(),
215            32
216        );
217        assert!(
218            from_request::<_, Path<MyStruct>>(&(), &req, &mut pl)
219                .await
220                .is_err()
221        );
222    }
223
224    #[crate::rt_test]
225    async fn test_tuple_extract() {
226        let mut router = Router::<usize>::builder();
227        router.path("/{key}/{value}/", 10).set_id(0);
228        let router = router.build();
229
230        let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
231        router.recognize(req.match_info_mut());
232
233        let (req, mut pl, ()) = req.into_parts();
234        let res = from_request::<_, (Path<(String, String)>,)>(&(), &req, &mut pl)
235            .await
236            .unwrap();
237        assert_eq!((res.0).0, "name");
238        assert_eq!((res.0).1, "user1");
239
240        let res =
241            from_request::<_, (Path<(String, String)>, Path<(String, String)>)>(&(), &req, &mut pl)
242                .await
243                .unwrap();
244        assert_eq!((res.0).0, "name");
245        assert_eq!((res.0).1, "user1");
246        assert_eq!((res.1).0, "name");
247        assert_eq!((res.1).1, "user1");
248
249        from_request::<_, ()>(&(), &req, &mut pl).await.unwrap();
250    }
251
252    #[crate::rt_test]
253    async fn test_request_extract() {
254        let mut router = Router::<usize>::builder();
255        router.path("/{key}/{value}/", 10).set_id(0);
256        let router = router.build();
257
258        let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
259        router.recognize(req.match_info_mut());
260
261        let (req, mut pl, ()) = req.into_parts();
262        let mut s = from_request::<_, Path<MyStruct>>(&(), &req, &mut pl)
263            .await
264            .unwrap();
265        assert_eq!(s.key, "name");
266        assert_eq!(s.value, "user1");
267        s.value = "user2".to_string();
268        assert_eq!(s.value, "user2");
269        assert_eq!(
270            format!("{s}, {s:?}"),
271            "MyStruct(name, user2), MyStruct { key: \"name\", value: \"user2\" }"
272        );
273        let s = s.into_inner();
274        assert_eq!(s.value, "user2");
275
276        let s = from_request::<_, Path<(String, String)>>(&(), &req, &mut pl)
277            .await
278            .unwrap();
279        assert_eq!(s.0, "name");
280        assert_eq!(s.1, "user1");
281
282        let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
283        router.recognize(req.match_info_mut());
284
285        let (req, mut pl, ()) = req.into_parts();
286        let s = from_request::<_, Path<Test2>>(&(), &req, &mut pl)
287            .await
288            .unwrap();
289        assert_eq!(s.as_ref().key, "name");
290        assert_eq!(s.value, 32);
291
292        let s = from_request::<_, Path<(String, u8)>>(&(), &req, &mut pl)
293            .await
294            .unwrap();
295        assert_eq!(s.0, "name");
296        assert_eq!(s.1, 32);
297
298        let res = from_request::<_, Path<Vec<String>>>(&(), &req, &mut pl)
299            .await
300            .unwrap();
301        assert_eq!(res[0], "name".to_owned());
302        assert_eq!(res[1], "32".to_owned());
303    }
304
305    #[test]
306    fn test_path_from() {
307        let path = Path::from(10u32);
308        assert_eq!(*path, 10);
309        assert_eq!(path.into_inner(), 10);
310    }
311}