miku_http_util/request/
parser.rs

1//! HTTP request utilities: parser related.
2
3#[cfg(any(feature = "feat-integrate-axum", feature = "feat-integrate-tower"))]
4pub mod integration;
5
6use std::{
7    borrow::{Borrow, Cow},
8    collections::HashMap,
9    hash::Hash,
10    sync::Arc,
11};
12
13use macro_toolset::wrapper;
14
15#[deprecated(
16    since = "0.6.0",
17    note = "Renamed and deprecated, use [`Query`] instead."
18)]
19/// Renamed and deprecated, use [`Query`] instead.
20pub type Queries<'q> = Query<'q>;
21
22#[deprecated(
23    since = "0.6.0",
24    note = "Renamed and deprecated, use [`OwnedQuery`] instead."
25)]
26/// Renamed and deprecated, use [`OwnedQuery`] instead.
27pub type OwnedQueries = OwnedQuery;
28
29wrapper! {
30    #[derive(Debug, Clone)]
31    /// Helper for query string parsing.
32    ///
33    /// You may also need [`OwnedQuery`].
34    pub Query<'q>(HashMap<Cow<'q, str>, Cow<'q, str>, foldhash::fast::RandomState>)
35}
36
37impl<'q> Query<'q> {
38    #[cfg(feature = "feat-integrate-http")]
39    #[inline]
40    /// Parse query string from [`http::Uri`].
41    pub fn parse_uri(uri: &'q http::Uri) -> Option<Self> {
42        uri.query().map(Self::parse)
43    }
44
45    #[inline]
46    /// Parse query string.
47    pub fn parse(query: &'q str) -> Self {
48        use fluent_uri::encoding::{encoder::IQuery, EStr};
49
50        Self {
51            inner: EStr::<IQuery>::new(query)
52                .unwrap_or({
53                    #[cfg(feature = "feat-tracing")]
54                    tracing::warn!("Failed to parse `{query}`");
55
56                    EStr::EMPTY
57                })
58                .split('&')
59                .map(|pair| {
60                    pair.split_once('=').unwrap_or({
61                        #[cfg(feature = "feat-tracing")]
62                        tracing::warn!("Failed to split query pair: {:?}", pair);
63
64                        (pair, EStr::EMPTY)
65                    })
66                })
67                .map(|(k, v)| {
68                    (
69                        k.decode().into_string_lossy(),
70                        v.decode().into_string_lossy(),
71                    )
72                })
73                .collect::<HashMap<_, _, _>>(),
74        }
75    }
76}
77
78wrapper! {
79    #[derive(Debug, Clone)]
80    /// Helper for query string parsing.
81    ///
82    /// You may also need [`Query`] if you just want a borrowed version.
83    pub OwnedQuery(Arc<HashMap<Arc<str>, Arc<str>, foldhash::fast::RandomState>>)
84}
85
86impl OwnedQuery {
87    #[cfg(feature = "feat-integrate-http")]
88    #[inline]
89    /// Parse query string from [`http::Uri`].
90    pub fn parse_uri(uri: &http::Uri) -> Option<Self> {
91        uri.query().map(Self::parse)
92    }
93
94    #[allow(clippy::multiple_bound_locations)]
95    #[inline]
96    /// Since [`OwnedQuery`] is a wrapper of [`Arc<HashMap<Arc<str>,
97    /// Arc<str>>>`] and implements `Deref`, without this you can still call
98    /// [`HashMap::get`] (though auto deref), however you will get an
99    /// `Option<&Arc<str>>`, and `&Arc<str>` is probably not what you want.
100    ///
101    /// Here's an example:
102    ///
103    /// ```ignore
104    /// let data: OwnedQuery = ...;
105    /// let example = data.get("example").unwrap(); // &Arc<str>
106    /// assert!(example, "example");
107    /// ```
108    ///
109    /// `assert!(example, "example")` will not compile at all, you must change
110    /// it to `assert!(&**example, "example")`:
111    ///
112    /// ```ignore
113    /// & * *example
114    /// ││└ &Arc<str> deref to Arc<str>
115    /// │└ Arc<str> deref to str
116    /// └ &str
117    /// ```
118    ///
119    /// This is really not convenient and graceful, so we provide this method as
120    /// an replacement of [`HashMap::get`].
121    /// See [*The Rustonomicon - The Dot Operator*](https://doc.rust-lang.org/nomicon/dot-operator.html) for the reason why we can do so.
122    pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&str>
123    where
124        Arc<str>: Borrow<Q>,
125        Q: Hash + Eq,
126    {
127        self.inner.get(k).map(|v| &**v)
128    }
129
130    #[inline]
131    /// Parse query string.
132    pub fn parse(query: &str) -> Self {
133        use fluent_uri::encoding::{encoder::IQuery, EStr};
134
135        Self {
136            inner: EStr::<IQuery>::new(query)
137                .unwrap_or({
138                    #[cfg(feature = "feat-tracing")]
139                    tracing::warn!("Failed to parse `{query}`");
140
141                    EStr::EMPTY
142                })
143                .split('&')
144                .map(|pair| {
145                    pair.split_once('=').unwrap_or({
146                        #[cfg(feature = "feat-tracing")]
147                        tracing::warn!("Failed to split query pair: {:?}", pair);
148
149                        (pair, EStr::EMPTY)
150                    })
151                })
152                .map(|(k, v)| {
153                    (
154                        k.decode().into_string_lossy().into(),
155                        v.decode().into_string_lossy().into(),
156                    )
157                })
158                .collect::<HashMap<_, _, _>>()
159                .into(),
160        }
161    }
162}