axum_extra/extract/
query.rs

1use axum_core::__composite_rejection as composite_rejection;
2use axum_core::__define_rejection as define_rejection;
3use axum_core::extract::FromRequestParts;
4use http::{request::Parts, Uri};
5use serde_core::de::DeserializeOwned;
6
7/// Extractor that deserializes query strings into some type.
8///
9/// `T` is expected to implement [`serde::Deserialize`].
10///
11/// # Differences from `axum::extract::Query`
12///
13/// This extractor uses [`serde_html_form`] under-the-hood which supports multi-value items. These
14/// are sent by multiple `<input>` attributes of the same name (e.g. checkboxes) and `<select>`s
15/// with the `multiple` attribute. Those values can be collected into a `Vec` or other sequential
16/// container.
17///
18/// # Example
19///
20/// ```rust,no_run
21/// use axum::{routing::get, Router};
22/// use axum_extra::extract::Query;
23/// use serde::Deserialize;
24///
25/// #[derive(Deserialize)]
26/// struct Pagination {
27///     page: usize,
28///     per_page: usize,
29/// }
30///
31/// // This will parse query strings like `?page=2&per_page=30` into `Pagination`
32/// // structs.
33/// async fn list_things(pagination: Query<Pagination>) {
34///     let pagination: Pagination = pagination.0;
35///
36///     // ...
37/// }
38///
39/// let app = Router::new().route("/list_things", get(list_things));
40/// # let _: Router = app;
41/// ```
42///
43/// If the query string cannot be parsed it will reject the request with a `400
44/// Bad Request` response.
45///
46/// For handling values being empty vs missing see the [query-params-with-empty-strings][example]
47/// example.
48///
49/// [example]: https://github.com/tokio-rs/axum/blob/main/examples/query-params-with-empty-strings/src/main.rs
50///
51/// While `Option<T>` will handle empty parameters (e.g. `param=`), beware when using this with a
52/// `Vec<T>`. If your list is optional, use `Vec<T>` in combination with `#[serde(default)]`
53/// instead of `Option<Vec<T>>`. `Option<Vec<T>>` will handle 0, 2, or more arguments, but not one
54/// argument.
55///
56/// # Example
57///
58/// ```rust,no_run
59/// use axum::{routing::get, Router};
60/// use axum_extra::extract::Query;
61/// use serde::Deserialize;
62///
63/// #[derive(Deserialize)]
64/// struct Params {
65///     #[serde(default)]
66///     items: Vec<usize>,
67/// }
68///
69/// // This will parse 0 occurrences of `items` as an empty `Vec`.
70/// async fn process_items(Query(params): Query<Params>) {
71///     // ...
72/// }
73///
74/// let app = Router::new().route("/process_items", get(process_items));
75/// # let _: Router = app;
76/// ```
77#[cfg_attr(docsrs, doc(cfg(feature = "query")))]
78#[derive(Debug, Clone, Copy, Default)]
79pub struct Query<T>(pub T);
80
81impl<T, S> FromRequestParts<S> for Query<T>
82where
83    T: DeserializeOwned,
84    S: Send + Sync,
85{
86    type Rejection = QueryRejection;
87
88    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
89        let query = parts.uri.query().unwrap_or_default();
90        let deserializer =
91            serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
92        let value = serde_path_to_error::deserialize(deserializer)
93            .map_err(FailedToDeserializeQueryString::from_err)?;
94        Ok(Query(value))
95    }
96}
97
98impl<T> Query<T>
99where
100    T: DeserializeOwned,
101{
102    /// Attempts to construct a [`Query`] from a reference to a [`Uri`].
103    ///
104    /// # Example
105    /// ```
106    /// use axum_extra::extract::Query;
107    /// use http::Uri;
108    /// use serde::Deserialize;
109    ///
110    /// #[derive(Deserialize)]
111    /// struct ExampleParams {
112    ///     foo: String,
113    ///     bar: u32,
114    /// }
115    ///
116    /// let uri: Uri = "http://example.com/path?foo=hello&bar=42".parse().unwrap();
117    /// let result: Query<ExampleParams> = Query::try_from_uri(&uri).unwrap();
118    /// assert_eq!(result.foo, String::from("hello"));
119    /// assert_eq!(result.bar, 42);
120    /// ```
121    pub fn try_from_uri(value: &Uri) -> Result<Self, QueryRejection> {
122        let query = value.query().unwrap_or_default();
123        let params =
124            serde_html_form::from_str(query).map_err(FailedToDeserializeQueryString::from_err)?;
125        Ok(Self(params))
126    }
127}
128
129axum_core::__impl_deref!(Query);
130
131define_rejection! {
132    #[status = BAD_REQUEST]
133    #[body = "Failed to deserialize query string"]
134    /// Rejection type used if the [`Query`] extractor is unable to
135    /// deserialize the query string into the target type.
136    pub struct FailedToDeserializeQueryString(Error);
137}
138
139composite_rejection! {
140    /// Rejection used for [`Query`].
141    ///
142    /// Contains one variant for each way the [`Query`] extractor can fail.
143    pub enum QueryRejection {
144        FailedToDeserializeQueryString,
145    }
146}
147
148/// Extractor that deserializes query strings into `None` if no query parameters are present.
149///
150/// Otherwise behaviour is identical to [`Query`].
151/// `T` is expected to implement [`serde::Deserialize`].
152///
153/// # Example
154///
155/// ```rust,no_run
156/// use axum::{routing::get, Router};
157/// use axum_extra::extract::OptionalQuery;
158/// use serde::Deserialize;
159///
160/// #[derive(Deserialize)]
161/// struct Pagination {
162///     page: usize,
163///     per_page: usize,
164/// }
165///
166/// // This will parse query strings like `?page=2&per_page=30` into `Some(Pagination)` and
167/// // empty query string into `None`
168/// async fn list_things(OptionalQuery(pagination): OptionalQuery<Pagination>) {
169///     match pagination {
170///         Some(Pagination{ page, per_page }) => { /* return specified page */ },
171///         None => { /* return fist page */ }
172///     }
173///     // ...
174/// }
175///
176/// let app = Router::new().route("/list_things", get(list_things));
177/// # let _: Router = app;
178/// ```
179///
180/// If the query string cannot be parsed it will reject the request with a `400
181/// Bad Request` response.
182///
183/// For handling values being empty vs missing see the [query-params-with-empty-strings][example]
184/// example.
185///
186/// [example]: https://github.com/tokio-rs/axum/blob/main/examples/query-params-with-empty-strings/src/main.rs
187#[cfg_attr(docsrs, doc(cfg(feature = "query")))]
188#[derive(Debug, Clone, Copy, Default)]
189pub struct OptionalQuery<T>(pub Option<T>);
190
191impl<T, S> FromRequestParts<S> for OptionalQuery<T>
192where
193    T: DeserializeOwned,
194    S: Send + Sync,
195{
196    type Rejection = OptionalQueryRejection;
197
198    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
199        if let Some(query) = parts.uri.query() {
200            let deserializer =
201                serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
202            let value = serde_path_to_error::deserialize(deserializer)
203                .map_err(FailedToDeserializeQueryString::from_err)?;
204            Ok(OptionalQuery(Some(value)))
205        } else {
206            Ok(OptionalQuery(None))
207        }
208    }
209}
210
211impl<T> std::ops::Deref for OptionalQuery<T> {
212    type Target = Option<T>;
213
214    #[inline]
215    fn deref(&self) -> &Self::Target {
216        &self.0
217    }
218}
219
220impl<T> std::ops::DerefMut for OptionalQuery<T> {
221    #[inline]
222    fn deref_mut(&mut self) -> &mut Self::Target {
223        &mut self.0
224    }
225}
226
227composite_rejection! {
228    /// Rejection used for [`OptionalQuery`].
229    ///
230    /// Contains one variant for each way the [`OptionalQuery`] extractor can fail.
231    pub enum OptionalQueryRejection {
232        FailedToDeserializeQueryString,
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use crate::test_helpers::*;
240    use axum::routing::{get, post};
241    use axum::Router;
242    use http::header::CONTENT_TYPE;
243    use http::StatusCode;
244    use serde::Deserialize;
245
246    #[tokio::test]
247    async fn query_supports_multiple_values() {
248        #[derive(Deserialize)]
249        struct Data {
250            #[serde(rename = "value")]
251            values: Vec<String>,
252        }
253
254        let app = Router::new().route(
255            "/",
256            post(|Query(data): Query<Data>| async move { data.values.join(",") }),
257        );
258
259        let client = TestClient::new(app);
260
261        let res = client
262            .post("/?value=one&value=two")
263            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
264            .body("")
265            .await;
266
267        assert_eq!(res.status(), StatusCode::OK);
268        assert_eq!(res.text().await, "one,two");
269    }
270
271    #[tokio::test]
272    async fn correct_rejection_status_code() {
273        #[derive(Deserialize)]
274        #[allow(dead_code)]
275        struct Params {
276            n: i32,
277        }
278
279        async fn handler(_: Query<Params>) {}
280
281        let app = Router::new().route("/", get(handler));
282        let client = TestClient::new(app);
283
284        let res = client.get("/?n=hi").await;
285        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
286        assert_eq!(
287            res.text().await,
288            "Failed to deserialize query string: n: invalid digit found in string"
289        );
290    }
291
292    #[tokio::test]
293    async fn optional_query_supports_multiple_values() {
294        #[derive(Deserialize)]
295        struct Data {
296            #[serde(rename = "value")]
297            values: Vec<String>,
298        }
299
300        let app = Router::new().route(
301            "/",
302            post(|OptionalQuery(data): OptionalQuery<Data>| async move {
303                data.map(|Data { values }| values.join(","))
304                    .unwrap_or("None".to_owned())
305            }),
306        );
307
308        let client = TestClient::new(app);
309
310        let res = client
311            .post("/?value=one&value=two")
312            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
313            .body("")
314            .await;
315
316        assert_eq!(res.status(), StatusCode::OK);
317        assert_eq!(res.text().await, "one,two");
318    }
319
320    #[tokio::test]
321    async fn optional_query_deserializes_no_parameters_into_none() {
322        #[derive(Deserialize)]
323        struct Data {
324            value: String,
325        }
326
327        let app = Router::new().route(
328            "/",
329            post(|OptionalQuery(data): OptionalQuery<Data>| async move {
330                match data {
331                    None => "None".into(),
332                    Some(data) => data.value,
333                }
334            }),
335        );
336
337        let client = TestClient::new(app);
338
339        let res = client.post("/").body("").await;
340
341        assert_eq!(res.status(), StatusCode::OK);
342        assert_eq!(res.text().await, "None");
343    }
344
345    #[tokio::test]
346    async fn optional_query_preserves_parsing_errors() {
347        #[derive(Deserialize)]
348        struct Data {
349            value: String,
350        }
351
352        let app = Router::new().route(
353            "/",
354            post(|OptionalQuery(data): OptionalQuery<Data>| async move {
355                match data {
356                    None => "None".into(),
357                    Some(data) => data.value,
358                }
359            }),
360        );
361
362        let client = TestClient::new(app);
363
364        let res = client
365            .post("/?other=something")
366            .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
367            .body("")
368            .await;
369
370        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
371    }
372
373    #[test]
374    fn test_try_from_uri() {
375        #[derive(Deserialize)]
376        struct TestQueryParams {
377            foo: Vec<String>,
378            bar: u32,
379        }
380        let uri: Uri = "http://example.com/path?foo=hello&bar=42&foo=goodbye"
381            .parse()
382            .unwrap();
383        let result: Query<TestQueryParams> = Query::try_from_uri(&uri).unwrap();
384        assert_eq!(result.foo, [String::from("hello"), String::from("goodbye")]);
385        assert_eq!(result.bar, 42);
386    }
387
388    #[test]
389    fn test_try_from_uri_with_invalid_query() {
390        #[derive(Deserialize)]
391        struct TestQueryParams {
392            _foo: String,
393            _bar: u32,
394        }
395        let uri: Uri = "http://example.com/path?foo=hello&bar=invalid"
396            .parse()
397            .unwrap();
398        let result: Result<Query<TestQueryParams>, _> = Query::try_from_uri(&uri);
399
400        assert!(result.is_err());
401    }
402}