actix_web_lab/
query.rs

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