pavex/request/query/
query_params.rs

1use pavex_macros::methods;
2
3use crate::request::RequestHead;
4
5use super::errors::{ExtractQueryParamsError, QueryDeserializationError};
6
7/// Extract (typed) query parameters from the query of an incoming request.
8///
9/// # Guide
10///
11/// Check out [the guide](https://pavex.dev/docs/guide/request_data/query/query_parameters/)
12/// for more information on how to use this extractor.
13///
14/// # Example
15///
16/// ```rust
17/// use pavex::request::query::QueryParams;
18/// // You must derive `serde::Deserialize` for the type you want to extract,
19/// // in this case `Home`.
20/// #[derive(serde::Deserialize)]
21/// pub struct Home {
22///     home_id: u32
23/// }
24///
25/// // The `QueryParams` extractor deserializes the extracted query parameters into
26/// // the type you specified—`Home` in this case.
27/// pub fn get_home(params: &QueryParams<Home>) -> String {
28///    format!("The identifier for this home is: {}", params.0.home_id)
29/// }
30/// ```
31///
32/// The `home_id` field will be set to `1` for the `?home_id=1` query string.
33///
34#[doc(alias = "Query")]
35#[doc(alias = "QueryParameters")]
36pub struct QueryParams<T>(
37    /// The extracted query parameters, deserialized into `T`, the type you specified.
38    pub T,
39);
40
41#[methods]
42impl<T> QueryParams<T> {
43    /// The default constructor for [`QueryParams`].
44    ///
45    /// If the extraction fails, an [`ExtractQueryParamsError`] is returned.
46    ///
47    /// Check out [`QueryParams`] for more information on query parameters.
48    #[request_scoped(pavex = crate, id = "QUERY_PARAMS_EXTRACT")]
49    pub fn extract<'request>(
50        request_head: &'request RequestHead,
51    ) -> Result<Self, ExtractQueryParamsError>
52    where
53        T: serde::Deserialize<'request>,
54    {
55        let query = request_head.target.query().unwrap_or_default();
56        parse(query).map(QueryParams)
57    }
58}
59
60/// Parse a query string into a `T`.
61fn parse<'a, T>(s: &'a str) -> Result<T, ExtractQueryParamsError>
62where
63    T: serde::Deserialize<'a>,
64{
65    let deserializer = serde_html_form::Deserializer::new(form_urlencoded::parse(s.as_bytes()));
66    serde_path_to_error::deserialize(deserializer)
67        .map_err(QueryDeserializationError::new)
68        .map_err(ExtractQueryParamsError::QueryDeserializationError)
69}
70
71#[cfg(test)]
72mod tests {
73    use std::borrow::Cow;
74
75    use super::*;
76
77    #[test]
78    fn test_parse() {
79        #[derive(serde::Deserialize, Debug, PartialEq)]
80        struct Home<'a> {
81            home_id: u32,
82            home_price: f64,
83            home_name: Cow<'a, str>,
84        }
85
86        let query = "home_id=1&home_price=0.1&home_name=Hi%20there";
87        let expected = Home {
88            home_id: 1,
89            home_price: 0.1,
90            home_name: Cow::Borrowed("Hi there"),
91        };
92        let actual: Home = parse(query).unwrap();
93        assert_eq!(expected, actual);
94    }
95
96    #[test]
97    fn test_sequence() {
98        #[derive(serde::Deserialize, Debug, PartialEq)]
99        struct Home {
100            room_ids: Vec<u32>,
101        }
102
103        let query = "room_ids=1&room_ids=2";
104        let expected = Home {
105            room_ids: vec![1, 2],
106        };
107        let actual: Home = parse(query).unwrap();
108        assert_eq!(expected, actual);
109    }
110
111    #[test]
112    fn test_sequence_with_brackets() {
113        #[derive(serde::Deserialize, Debug, PartialEq)]
114        struct Home {
115            #[serde(rename = "room_ids[]")]
116            room_ids: Vec<u32>,
117        }
118
119        let query = "room_ids[]=1&room_ids[]=2";
120        let expected = Home {
121            room_ids: vec![1, 2],
122        };
123        let actual: Home = parse(query).unwrap();
124        assert_eq!(expected, actual);
125    }
126}