Skip to main content

rama_http/service/web/endpoint/extract/
query.rs

1//! Module in function of the [`Query`] extractor.
2
3use super::{FromPartsStateRefPair, OptionalFromPartsStateRefPair};
4use crate::request::Parts;
5use crate::utils::macros::define_http_rejection;
6use rama_net::uri::Uri;
7use serde::de::DeserializeOwned;
8
9/// Extractor that deserializes query strings into some type.
10///
11/// `T` is expected to implement [`serde::Deserialize`].
12#[derive(Debug, Clone)]
13pub struct Query<T>(pub T);
14
15define_http_rejection! {
16    #[status = BAD_REQUEST]
17    #[body = "Failed to deserialize query string"]
18    /// Rejection type used if the [`Query`] extractor is unable to
19    /// deserialize the query string into the target type.
20    pub struct FailedToDeserializeQueryString(Error);
21}
22
23impl<T> Query<T>
24where
25    T: DeserializeOwned + Send + Sync + 'static,
26{
27    /// Attempts to construct a [`Query`] from a reference to a [`Uri`].
28    #[inline(always)]
29    pub fn try_from_uri(value: &Uri) -> Result<Self, FailedToDeserializeQueryString> {
30        value
31            .query_params::<T>()
32            .map(Self)
33            .map_err(FailedToDeserializeQueryString::from_err)
34    }
35
36    /// Create a `Query<T>` directly from the query str,
37    /// can be useful to combine this method as part of another extractor
38    /// or otherwise impossible combination.
39    pub fn parse_query_str(query: &str) -> Result<Self, FailedToDeserializeQueryString> {
40        let params =
41            serde_html_form::from_str(query).map_err(FailedToDeserializeQueryString::from_err)?;
42        Ok(Self(params))
43    }
44}
45
46impl<T, State> FromPartsStateRefPair<State> for Query<T>
47where
48    T: DeserializeOwned + Send + Sync + 'static,
49    State: Send + Sync,
50{
51    type Rejection = FailedToDeserializeQueryString;
52
53    async fn from_parts_state_ref_pair(
54        parts: &Parts,
55        _state: &State,
56    ) -> Result<Self, Self::Rejection> {
57        parts
58            .uri
59            .query_params::<T>()
60            .map(Self)
61            .map_err(FailedToDeserializeQueryString::from_err)
62    }
63}
64
65impl<T, State> OptionalFromPartsStateRefPair<State> for Query<T>
66where
67    T: DeserializeOwned + Send + Sync + 'static,
68    State: Send + Sync,
69{
70    type Rejection = FailedToDeserializeQueryString;
71
72    async fn from_parts_state_ref_pair(
73        parts: &Parts,
74        _state: &State,
75    ) -> Result<Option<Self>, Self::Rejection> {
76        match parts.uri.query() {
77            Some(_) => parts
78                .uri
79                .query_params::<T>()
80                .map(|params| Some(Self(params)))
81                .map_err(FailedToDeserializeQueryString::from_err),
82            None => Ok(None),
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use serde::Deserialize;
91
92    #[test]
93    fn test_try_from_uri() {
94        #[derive(Deserialize)]
95        struct TestQueryParams {
96            foo: Vec<String>,
97            bar: u32,
98        }
99        let uri: Uri = "http://example.com/path?foo=hello&bar=42&foo=goodbye"
100            .parse()
101            .unwrap();
102        let Query(TestQueryParams { foo, bar }) = Query::try_from_uri(&uri).unwrap();
103        assert_eq!(foo, [String::from("hello"), String::from("goodbye")]);
104        assert_eq!(bar, 42);
105    }
106
107    #[test]
108    fn test_try_from_uri_with_invalid_query() {
109        #[derive(Deserialize)]
110        struct TestQueryParams {
111            _foo: String,
112            _bar: u32,
113        }
114        let uri: Uri = "http://example.com/path?foo=hello&bar=invalid"
115            .parse()
116            .unwrap();
117        let result: Result<Query<TestQueryParams>, _> = Query::try_from_uri(&uri);
118
119        assert!(result.is_err());
120    }
121}