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}