Skip to main content

ntex/web/types/
query.rs

1//! Query extractor
2use std::{fmt, ops};
3
4use serde::de;
5
6use crate::http::Payload;
7use crate::web::{FromRequest, HttpRequest, State, error::QueryPayloadError};
8
9/// Extract typed information from the request's query.
10///
11/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot
12/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs.
13/// Attempts to do so will *fail at runtime*.
14///
15/// If the query string cannot be deserialized into `T`, extraction fails with
16/// [`QueryPayloadError`](crate::web::error::QueryPayloadError), which the default
17/// error domain renders as `400 Bad Request`.
18///
19/// ## Example
20///
21/// ```rust
22/// use ntex::web;
23///
24/// #[derive(Debug, serde::Deserialize)]
25/// pub enum ResponseType {
26///    Token,
27///    Code
28/// }
29///
30/// #[derive(serde::Deserialize)]
31/// pub struct AuthRequest {
32///    id: u64,
33///    response_type: ResponseType,
34/// }
35///
36/// // Use `Query` extractor for query information (and destructure it within the signature).
37/// // If `id` or `response_type` is missing or invalid, extraction fails and the
38/// // handler is not called.
39/// // The correct request for this handler would be `/index.html?id=64&response_type=Code`.
40/// async fn index(web::types::Query(info): web::types::Query<AuthRequest>) -> String {
41///     format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
42/// }
43///
44/// fn main() {
45///     let app = web::App::default().service(
46///        web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
47/// }
48/// ```
49#[derive(PartialEq, Eq, PartialOrd, Ord)]
50pub struct Query<T>(pub T);
51
52impl<T> Query<T> {
53    /// Deconstruct to a inner value
54    pub fn into_inner(self) -> T {
55        self.0
56    }
57
58    /// Get query parameters from the path
59    pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError>
60    where
61        T: de::DeserializeOwned,
62    {
63        serde_urlencoded::from_str::<T>(query_str).map_or_else(
64            |e| Err(QueryPayloadError::Deserialize(e)),
65            |val| Ok(Query(val)),
66        )
67    }
68}
69
70impl<T> ops::Deref for Query<T> {
71    type Target = T;
72
73    fn deref(&self) -> &T {
74        &self.0
75    }
76}
77
78impl<T> ops::DerefMut for Query<T> {
79    fn deref_mut(&mut self) -> &mut T {
80        &mut self.0
81    }
82}
83
84impl<T: fmt::Debug> fmt::Debug for Query<T> {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        self.0.fmt(f)
87    }
88}
89
90impl<T: fmt::Display> fmt::Display for Query<T> {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        self.0.fmt(f)
93    }
94}
95
96/// Extract typed information from the request's query.
97///
98/// ## Example
99///
100/// ```rust
101/// use ntex::web;
102///
103/// #[derive(Debug, serde::Deserialize)]
104/// pub enum ResponseType {
105///    Token,
106///    Code
107/// }
108///
109/// #[derive(serde::Deserialize)]
110/// pub struct AuthRequest {
111///    id: u64,
112///    response_type: ResponseType,
113/// }
114///
115/// // Use `Query` extractor for query information.
116/// // If `id` or `response_type` is missing or invalid, extraction fails and the
117/// // handler is not called.
118/// // The correct request for this handler would be `/index.html?id=64&response_type=Code`
119/// async fn index(info: web::types::Query<AuthRequest>) -> String {
120///     format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
121/// }
122///
123/// fn main() {
124///     let app = web::App::default().service(
125///        web::resource("/index.html")
126///            .route(web::get().to(index))); // <- use `Query` extractor
127/// }
128/// ```
129impl<St, T> FromRequest<St> for Query<T>
130where
131    St: State,
132    T: de::DeserializeOwned,
133{
134    type Error = QueryPayloadError;
135
136    #[inline]
137    async fn from_request(_: &St, req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
138        serde_urlencoded::from_str::<T>(req.query_string()).map_or_else(
139            move |e| {
140                let e = QueryPayloadError::Deserialize(e);
141                log::debug!(
142                    "Failed during Query extractor deserialization. \
143                     Request path: {:?}",
144                    req.path()
145                );
146                Err(e)
147            },
148            |val| Ok(Query(val)),
149        )
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use crate::web::test::{TestRequest, from_request};
157
158    #[derive(serde::Deserialize, Debug, thiserror::Error)]
159    #[error("Id({id})")]
160    struct Id {
161        id: String,
162    }
163
164    #[crate::rt_test]
165    async fn test_service_request_extract() {
166        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
167        assert!(Query::<Id>::from_query(req.query_string()).is_err());
168
169        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
170        let mut s = Query::<Id>::from_query(req.query_string()).unwrap();
171
172        assert_eq!(s.id, "test");
173        assert_eq!(format!("{s}, {s:?}"), "Id(test), Id { id: \"test\" }");
174
175        s.id = "test1".to_string();
176        let s = s.into_inner();
177        assert_eq!(s.id, "test1");
178    }
179
180    #[crate::rt_test]
181    async fn test_request_extract() {
182        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
183        let (req, mut pl, ()) = req.into_parts();
184        let res = from_request::<_, Query<Id>>(&(), &req, &mut pl).await;
185        assert!(res.is_err());
186
187        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
188        let (req, mut pl, ()) = req.into_parts();
189
190        let mut s = from_request::<_, Query<Id>>(&(), &req, &mut pl)
191            .await
192            .unwrap();
193        assert_eq!(s.id, "test");
194        assert_eq!(format!("{s}, {s:?}"), "Id(test), Id { id: \"test\" }");
195
196        s.id = "test1".to_string();
197        let s = s.into_inner();
198        assert_eq!(s.id, "test1");
199    }
200}