actix_web_lab/
query.rs

1//! For query parameter extractor documentation, see [`Query`].
2
3use std::future::{ready, Ready};
4
5use actix_web::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
6use serde::de::DeserializeOwned;
7use tracing::debug;
8
9/// Extract typed information from the request's query.
10///
11/// To extract typed data from the URL query string, the inner type `T` must implement the
12/// [`DeserializeOwned`] trait.
13///
14/// # Differences From `actix_web::web::Query`
15/// This extractor uses `serde_html_form` under-the-hood which supports multi-value items. These are
16/// sent by HTML select inputs when multiple options are chosen and can be collected into a `Vec`.
17///
18/// This version also removes the custom error handler config; users should instead prefer to handle
19/// errors using the explicit `Result<Query<T>, E>` extractor in their handlers.
20///
21/// # Panics
22/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any
23/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
24///
25/// # Examples
26/// ```
27/// use actix_web::{get, Responder};
28/// use actix_web_lab::extract::Query;
29/// use serde::Deserialize;
30///
31/// #[derive(Debug, Deserialize)]
32/// #[serde(rename_all = "lowercase")]
33/// enum LogType {
34///     Reports,
35///     Actions,
36/// }
37///
38/// #[derive(Debug, Deserialize)]
39/// pub struct LogsParams {
40///     #[serde(rename = "type")]
41///     log_type: u64,
42///
43///     #[serde(rename = "user")]
44///     users: Vec<String>,
45/// }
46///
47/// // Deserialize `LogsParams` struct from query string.
48/// // This handler gets called only if the request's query parameters contain both fields.
49/// // A valid request path for this handler would be `/logs?type=reports&user=foo&user=bar"`.
50/// #[get("/logs")]
51/// async fn index(info: Query<LogsParams>) -> impl Responder {
52///     let LogsParams { log_type, users } = info.into_inner();
53///     format!("Logs request for type={log_type} and user list={users:?}!")
54/// }
55///
56/// // Or use destructuring, which is equivalent to `.into_inner()`.
57/// #[get("/debug2")]
58/// async fn debug2(Query(info): Query<LogsParams>) -> impl Responder {
59///     dbg!("Authorization object = {info:?}");
60///     "OK"
61/// }
62/// ```
63#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
64pub struct Query<T>(pub T);
65
66impl_more::impl_deref_and_mut!(<T> in Query<T> => T);
67impl_more::forward_display!(<T> in Query<T>);
68
69impl<T> Query<T> {
70    /// Unwrap into inner `T` value.
71    pub fn into_inner(self) -> T {
72        self.0
73    }
74}
75
76impl<T: DeserializeOwned> Query<T> {
77    /// Deserialize a `T` from the URL encoded query parameter string.
78    ///
79    /// ```
80    /// # use std::collections::HashMap;
81    /// # use actix_web_lab::extract::Query;
82    /// let numbers = Query::<HashMap<String, u32>>::from_query("one=1&two=2").unwrap();
83    ///
84    /// assert_eq!(numbers.get("one"), Some(&1));
85    /// assert_eq!(numbers.get("two"), Some(&2));
86    /// assert!(numbers.get("three").is_none());
87    /// ```
88    pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError> {
89        serde_html_form::from_str::<T>(query_str)
90            .map(Self)
91            .map_err(QueryPayloadError::Deserialize)
92    }
93}
94
95/// See [here](#examples) for example of usage as an extractor.
96impl<T: DeserializeOwned> FromRequest for Query<T> {
97    type Error = Error;
98    type Future = Ready<Result<Self, Error>>;
99
100    #[inline]
101    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
102        serde_html_form::from_str::<T>(req.query_string())
103            .map(|val| ready(Ok(Query(val))))
104            .unwrap_or_else(move |e| {
105                let err = QueryPayloadError::Deserialize(e);
106
107                debug!(
108                    "Failed during Query extractor deserialization. \
109                     Request path: {:?}",
110                    req.path()
111                );
112
113                ready(Err(err.into()))
114            })
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use actix_web::test::TestRequest;
121    use derive_more::Display;
122    use serde::Deserialize;
123
124    use super::*;
125
126    #[derive(Deserialize, Debug, Display)]
127    struct Id {
128        id: String,
129    }
130
131    #[actix_web::test]
132    async fn test_service_request_extract() {
133        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
134        assert!(Query::<Id>::from_query(req.query_string()).is_err());
135
136        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
137        let mut s = Query::<Id>::from_query(req.query_string()).unwrap();
138
139        assert_eq!(s.id, "test");
140        assert_eq!(format!("{s}, {s:?}"), "test, Query(Id { id: \"test\" })");
141
142        s.id = "test1".to_string();
143        let s = s.into_inner();
144        assert_eq!(s.id, "test1");
145    }
146
147    #[actix_web::test]
148    async fn extract_array() {
149        #[derive(Debug, Deserialize)]
150        struct Test {
151            #[serde(rename = "user")]
152            users: Vec<String>,
153        }
154
155        let req = TestRequest::with_uri("/?user=foo&user=bar").to_srv_request();
156        let s = Query::<Test>::from_query(req.query_string()).unwrap();
157
158        assert_eq!(s.users[0], "foo");
159        assert_eq!(s.users[1], "bar");
160    }
161
162    #[actix_web::test]
163    async fn test_request_extract() {
164        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
165        let (req, mut pl) = req.into_parts();
166        assert!(Query::<Id>::from_request(&req, &mut pl).await.is_err());
167
168        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
169        let (req, mut pl) = req.into_parts();
170
171        let mut s = Query::<Id>::from_request(&req, &mut pl).await.unwrap();
172        assert_eq!(s.id, "test");
173        assert_eq!(format!("{s}, {s:?}"), "test, Query(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    #[actix_web::test]
181    #[should_panic]
182    async fn test_tuple_panic() {
183        let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request();
184        let (req, mut pl) = req.into_parts();
185
186        Query::<(u32, u32)>::from_request(&req, &mut pl)
187            .await
188            .unwrap();
189    }
190}