1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! For query parameter extractor documentation, see [`Query`].

use std::future::{ready, Ready};

use actix_web::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
use serde::de::DeserializeOwned;
use tracing::debug;

/// Extract typed information from the request's query.
///
/// To extract typed data from the URL query string, the inner type `T` must implement the
/// [`DeserializeOwned`] trait.
///
/// # Differences From `actix_web::web::Query`
/// This extractor uses `serde_html_form` under-the-hood which supports multi-value items. These are
/// sent by HTML select inputs when multiple options are chosen and can be collected into a `Vec`.
///
/// This version also removes the custom error handler config; users should instead prefer to handle
/// errors using the explicit `Result<Query<T>, E>` extractor in their handlers.
///
/// # Panics
/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any
/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
///
/// # Examples
/// ```
/// use actix_web::{get, Responder};
/// use actix_web_lab::extract::Query;
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// #[serde(rename_all = "lowercase")]
/// enum LogType {
///     Reports,
///     Actions,
/// }
///
/// #[derive(Debug, Deserialize)]
/// pub struct LogsParams {
///    #[serde(rename = "type")]
///    log_type: u64,
///
///    #[serde(rename = "user")]
///    users: Vec<String>,
/// }
///
/// // Deserialize `LogsParams` struct from query string.
/// // This handler gets called only if the request's query parameters contain both fields.
/// // A valid request path for this handler would be `/logs?type=reports&user=foo&user=bar"`.
/// #[get("/logs")]
/// async fn index(info: Query<LogsParams>) -> impl Responder {
///     let LogsParams { log_type, users } = info.into_inner();
///     format!("Logs request for type={log_type} and user list={users:?}!")
/// }
///
/// // Or use destructuring, which is equivalent to `.into_inner()`.
/// #[get("/debug2")]
/// async fn debug2(Query(info): Query<LogsParams>) -> impl Responder {
///     dbg!("Authorization object = {info:?}");
///     "OK"
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Query<T>(pub T);

impl_more::impl_deref_and_mut!(<T> in Query<T> => T);
impl_more::forward_display!(<T> in Query<T>);

impl<T> Query<T> {
    /// Unwrap into inner `T` value.
    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T: DeserializeOwned> Query<T> {
    /// Deserialize a `T` from the URL encoded query parameter string.
    ///
    /// ```
    /// # use std::collections::HashMap;
    /// # use actix_web_lab::extract::Query;
    /// let numbers = Query::<HashMap<String, u32>>::from_query("one=1&two=2").unwrap();
    /// assert_eq!(numbers.get("one"), Some(&1));
    /// assert_eq!(numbers.get("two"), Some(&2));
    /// assert!(numbers.get("three").is_none());
    /// ```
    pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError> {
        serde_html_form::from_str::<T>(query_str)
            .map(Self)
            .map_err(QueryPayloadError::Deserialize)
    }
}

/// See [here](#examples) for example of usage as an extractor.
impl<T: DeserializeOwned> FromRequest for Query<T> {
    type Error = Error;
    type Future = Ready<Result<Self, Error>>;

    #[inline]
    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        serde_html_form::from_str::<T>(req.query_string())
            .map(|val| ready(Ok(Query(val))))
            .unwrap_or_else(move |e| {
                let err = QueryPayloadError::Deserialize(e);

                debug!(
                    "Failed during Query extractor deserialization. \
                     Request path: {:?}",
                    req.path()
                );

                ready(Err(err.into()))
            })
    }
}

#[cfg(test)]
mod tests {
    use actix_web::test::TestRequest;
    use derive_more::Display;
    use serde::Deserialize;

    use super::*;

    #[derive(Deserialize, Debug, Display)]
    struct Id {
        id: String,
    }

    #[actix_web::test]
    async fn test_service_request_extract() {
        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
        assert!(Query::<Id>::from_query(req.query_string()).is_err());

        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
        let mut s = Query::<Id>::from_query(req.query_string()).unwrap();

        assert_eq!(s.id, "test");
        assert_eq!(format!("{s}, {s:?}"), "test, Query(Id { id: \"test\" })");

        s.id = "test1".to_string();
        let s = s.into_inner();
        assert_eq!(s.id, "test1");
    }

    #[actix_web::test]
    async fn extract_array() {
        #[derive(Debug, Deserialize)]
        struct Test {
            #[serde(rename = "user")]
            users: Vec<String>,
        }

        let req = TestRequest::with_uri("/?user=foo&user=bar").to_srv_request();
        let s = Query::<Test>::from_query(req.query_string()).unwrap();

        assert_eq!(s.users[0], "foo");
        assert_eq!(s.users[1], "bar");
    }

    #[actix_web::test]
    async fn test_request_extract() {
        let req = TestRequest::with_uri("/name/user1/").to_srv_request();
        let (req, mut pl) = req.into_parts();
        assert!(Query::<Id>::from_request(&req, &mut pl).await.is_err());

        let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
        let (req, mut pl) = req.into_parts();

        let mut s = Query::<Id>::from_request(&req, &mut pl).await.unwrap();
        assert_eq!(s.id, "test");
        assert_eq!(format!("{s}, {s:?}"), "test, Query(Id { id: \"test\" })");

        s.id = "test1".to_string();
        let s = s.into_inner();
        assert_eq!(s.id, "test1");
    }

    #[actix_web::test]
    #[should_panic]
    async fn test_tuple_panic() {
        let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request();
        let (req, mut pl) = req.into_parts();

        Query::<(u32, u32)>::from_request(&req, &mut pl)
            .await
            .unwrap();
    }
}