Skip to main content

actix_web/types/
query.rs

1//! For query parameter extractor documentation, see [`Query`].
2
3use std::{fmt, ops, sync::Arc};
4
5#[expect(deprecated, reason = "preserve the public ready future type")]
6use actix_utils::future::{ready, Ready};
7use serde::de::DeserializeOwned;
8
9use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
10
11/// Extract typed information from the request's query.
12///
13/// To extract typed data from the URL query string, the inner type `T` must implement the
14/// [`DeserializeOwned`] trait.
15///
16/// Use [`QueryConfig`] to configure extraction process.
17///
18/// # Panics
19/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any
20/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
21///
22/// # Examples
23/// ```
24/// use actix_web::{get, web};
25/// use serde::Deserialize;
26///
27/// #[derive(Debug, Deserialize)]
28/// pub enum ResponseType {
29///    Token,
30///    Code
31/// }
32///
33/// #[derive(Debug, Deserialize)]
34/// pub struct AuthRequest {
35///    id: u64,
36///    response_type: ResponseType,
37/// }
38///
39/// // Deserialize `AuthRequest` struct from query string.
40/// // This handler gets called only if the request's query parameters contain both fields.
41/// // A valid request path for this handler would be `/?id=64&response_type=Code"`.
42/// #[get("/")]
43/// async fn index(info: web::Query<AuthRequest>) -> String {
44///     format!("Authorization request for id={} and type={:?}!", info.id, info.response_type)
45/// }
46///
47/// // To access the entire underlying query struct, use `.into_inner()`.
48/// #[get("/debug1")]
49/// async fn debug1(info: web::Query<AuthRequest>) -> String {
50///     dbg!("Authorization object = {:?}", info.into_inner());
51///     "OK".to_string()
52/// }
53///
54/// // Or use destructuring, which is equivalent to `.into_inner()`.
55/// #[get("/debug2")]
56/// async fn debug2(web::Query(info): web::Query<AuthRequest>) -> String {
57///     dbg!("Authorization object = {:?}", info);
58///     "OK".to_string()
59/// }
60/// ```
61#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
62pub struct Query<T>(pub T);
63
64impl<T> Query<T> {
65    /// Unwrap into inner `T` value.
66    pub fn into_inner(self) -> T {
67        self.0
68    }
69}
70
71impl<T: DeserializeOwned> Query<T> {
72    /// Deserialize a `T` from the URL encoded query parameter string.
73    ///
74    /// ```
75    /// # use std::collections::HashMap;
76    /// # use actix_web::web::Query;
77    /// let numbers = Query::<HashMap<String, u32>>::from_query("one=1&two=2").unwrap();
78    /// assert_eq!(numbers.get("one"), Some(&1));
79    /// assert_eq!(numbers.get("two"), Some(&2));
80    /// assert!(numbers.get("three").is_none());
81    /// ```
82    pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError> {
83        serde_urlencoded::from_str::<T>(query_str)
84            .map(Self)
85            .map_err(QueryPayloadError::Deserialize)
86    }
87}
88
89impl<T> ops::Deref for Query<T> {
90    type Target = T;
91
92    fn deref(&self) -> &T {
93        &self.0
94    }
95}
96
97impl<T> ops::DerefMut for Query<T> {
98    fn deref_mut(&mut self) -> &mut T {
99        &mut self.0
100    }
101}
102
103impl<T: fmt::Display> fmt::Display for Query<T> {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        self.0.fmt(f)
106    }
107}
108
109/// See [here](#Examples) for example of usage as an extractor.
110#[expect(deprecated, reason = "preserve the public ready future type")]
111impl<T: DeserializeOwned> FromRequest for Query<T> {
112    type Error = Error;
113    type Future = Ready<Result<Self, Error>>;
114
115    #[inline]
116    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
117        let error_handler = req
118            .app_data::<QueryConfig>()
119            .and_then(|c| c.err_handler.clone());
120
121        serde_urlencoded::from_str::<T>(req.query_string())
122            .map(|val| ready(Ok(Query(val))))
123            .unwrap_or_else(move |err| {
124                let err = QueryPayloadError::Deserialize(err);
125
126                log::debug!(
127                    "Failed during Query extractor deserialization. \
128                     Request path: {:?}",
129                    req.path()
130                );
131
132                let err = if let Some(error_handler) = error_handler {
133                    (error_handler)(err, req)
134                } else {
135                    err.into()
136                };
137
138                ready(Err(err))
139            })
140    }
141}
142
143/// Query extractor configuration.
144///
145/// # Examples
146/// ```
147/// use actix_web::{error, get, web, App, FromRequest, HttpResponse};
148/// use serde::Deserialize;
149///
150/// #[derive(Deserialize)]
151/// struct Info {
152///     username: String,
153/// }
154///
155/// /// deserialize `Info` from request's querystring
156/// #[get("/")]
157/// async fn index(info: web::Query<Info>) -> String {
158///     format!("Welcome {}!", info.username)
159/// }
160///
161/// // custom `Query` extractor configuration
162/// let query_cfg = web::QueryConfig::default()
163///     // use custom error handler
164///     .error_handler(|err, req| {
165///         error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
166///     });
167///
168/// App::new()
169///     .app_data(query_cfg)
170///     .service(index);
171/// ```
172#[derive(Clone, Default)]
173pub struct QueryConfig {
174    #[allow(clippy::type_complexity)]
175    err_handler: Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
176}
177
178impl QueryConfig {
179    /// Set custom error handler
180    pub fn error_handler<F>(mut self, f: F) -> Self
181    where
182        F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
183    {
184        self.err_handler = Some(Arc::new(f));
185        self
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use actix_http::StatusCode;
192    use derive_more::Display;
193    use serde::Deserialize;
194
195    use super::*;
196    use crate::{error::InternalError, test::TestRequest, HttpResponse};
197
198    #[derive(Deserialize, Debug, Display)]
199    struct Id {
200        id: String,
201    }
202
203    #[actix_rt::test]
204    async fn test_service_request_extract() {
205        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
206        assert!(Query::<Id>::from_query(req.query_string()).is_err());
207
208        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
209        let mut s = Query::<Id>::from_query(req.query_string()).unwrap();
210
211        assert_eq!(s.id, "test");
212        assert_eq!(
213            format!("{}, {:?}", s, s),
214            "test, Query(Id { id: \"test\" })"
215        );
216
217        s.id = "test1".to_string();
218        let s = s.into_inner();
219        assert_eq!(s.id, "test1");
220    }
221
222    #[actix_rt::test]
223    async fn test_request_extract() {
224        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
225        let (req, mut pl) = req.into_parts();
226        assert!(Query::<Id>::from_request(&req, &mut pl).await.is_err());
227
228        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
229        let (req, mut pl) = req.into_parts();
230
231        let mut s = Query::<Id>::from_request(&req, &mut pl).await.unwrap();
232        assert_eq!(s.id, "test");
233        assert_eq!(
234            format!("{}, {:?}", s, s),
235            "test, Query(Id { id: \"test\" })"
236        );
237
238        s.id = "test1".to_string();
239        let s = s.into_inner();
240        assert_eq!(s.id, "test1");
241    }
242
243    #[actix_rt::test]
244    #[should_panic]
245    async fn test_tuple_panic() {
246        let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request();
247        let (req, mut pl) = req.into_parts();
248
249        Query::<(u32, u32)>::from_request(&req, &mut pl)
250            .await
251            .unwrap();
252    }
253
254    #[actix_rt::test]
255    async fn test_custom_error_responder() {
256        let req = TestRequest::with_uri("/name/user1/")
257            .app_data(QueryConfig::default().error_handler(|e, _| {
258                let resp = HttpResponse::UnprocessableEntity().finish();
259                InternalError::from_response(e, resp).into()
260            }))
261            .to_srv_request();
262
263        let (req, mut pl) = req.into_parts();
264        let query = Query::<Id>::from_request(&req, &mut pl).await;
265
266        assert!(query.is_err());
267        assert_eq!(
268            query
269                .unwrap_err()
270                .as_response_error()
271                .error_response()
272                .status(),
273            StatusCode::UNPROCESSABLE_ENTITY
274        );
275    }
276}