Skip to main content

urly/
query.rs

1use std::borrow::Cow;
2
3use crate::chars::{self, QUERY};
4use crate::error::InvalidUrl;
5use crate::quoting::{Component, unquote};
6
7/// Percent-encoded URL query, without the leading `?`.
8#[repr(transparent)]
9pub struct Query(str);
10
11str_type!(Query);
12str_eq!(Query);
13
14impl Query {
15    /// Strictly validates a query: `*( pchar / "/" / "?" )`.
16    pub fn new(src: &str) -> Result<&Query, InvalidUrl> {
17        chars::check(src, &QUERY)?;
18        Ok(Query::from_str_unchecked(src))
19    }
20
21    /// Returns `true` if the query is empty.
22    pub fn is_empty(&self) -> bool {
23        self.0.is_empty()
24    }
25
26    /// Returns the decoded query. `+` is decoded as space; escapes of `&`, `=`,
27    /// `+` and `;` are kept.
28    pub fn decode(&self) -> Cow<'_, str> {
29        unquote(&self.0, Component::Query)
30    }
31
32    /// Returns an iterator over decoded `key=value` pairs.
33    ///
34    /// ```
35    /// use urly::Url;
36    ///
37    /// let url = Url::from_static("/?a=1&b=x+y&a=%32&flag");
38    /// let pairs: Vec<_> = url.query().unwrap().pairs().collect();
39    /// let pairs: Vec<_> = pairs.iter().map(|(k, v)| (&**k, &**v)).collect();
40    /// assert_eq!(pairs, [("a", "1"), ("b", "x y"), ("a", "2"), ("flag", "")]);
41    /// ```
42    pub fn pairs(&self) -> QueryPairs<'_> {
43        QueryPairs(self.0.split('&'))
44    }
45
46    /// Returns the first decoded value for `key`.
47    ///
48    /// ```
49    /// use urly::Url;
50    ///
51    /// let url = Url::from_static("/?a=1&a=2&b=%C3%BC");
52    /// let query = url.query().unwrap();
53    /// assert_eq!(query.get("a").unwrap(), "1");
54    /// assert_eq!(query.get("b").unwrap(), "ΓΌ");
55    /// assert_eq!(query.get_all("a").collect::<Vec<_>>(), ["1", "2"]);
56    /// assert!(!query.contains_key("c"));
57    /// ```
58    pub fn get<'a>(&'a self, key: &str) -> Option<Cow<'a, str>> {
59        self.get_all(key).next()
60    }
61
62    /// Returns all decoded values for `key`.
63    pub fn get_all<'a>(&'a self, key: &str) -> impl Iterator<Item = Cow<'a, str>> {
64        self.raw_values(key)
65            .map(|v| unquote(v, Component::QueryPart))
66    }
67
68    /// Returns `true` if the query contains `key`.
69    pub fn contains_key(&self, key: &str) -> bool {
70        self.raw_values(key).next().is_some()
71    }
72
73    fn raw_values<'a>(&'a self, key: &str) -> impl Iterator<Item = &'a str> {
74        pieces(&self.0)
75            .map(split_pair)
76            .filter_map(move |(k, v)| (unquote(k, Component::QueryPart) == key).then_some(v))
77    }
78}
79
80/// Splits a raw query into non-empty `key=value` pieces.
81pub(crate) fn pieces(query: &str) -> impl Iterator<Item = &str> {
82    query.split('&').filter(|piece| !piece.is_empty())
83}
84
85/// Splits a raw `key=value` piece; the value is empty if there is no `=`.
86pub(crate) fn split_pair(piece: &str) -> (&str, &str) {
87    piece.split_once('=').unwrap_or((piece, ""))
88}
89
90/// Iterator over decoded query pairs, see [`Query::pairs`].
91#[derive(Clone, Debug)]
92pub struct QueryPairs<'a>(std::str::Split<'a, char>);
93
94impl<'a> Iterator for QueryPairs<'a> {
95    type Item = (Cow<'a, str>, Cow<'a, str>);
96
97    fn next(&mut self) -> Option<Self::Item> {
98        let (k, v) = split_pair(self.0.by_ref().find(|piece| !piece.is_empty())?);
99        Some((
100            unquote(k, Component::QueryPart),
101            unquote(v, Component::QueryPart),
102        ))
103    }
104}
105
106/// Percent-encoded URL fragment, without the leading `#`.
107#[repr(transparent)]
108pub struct Fragment(str);
109
110str_type!(Fragment);
111str_eq!(Fragment);
112
113impl Fragment {
114    /// Strictly validates a fragment: `*( pchar / "/" / "?" )`.
115    pub fn new(src: &str) -> Result<&Fragment, InvalidUrl> {
116        chars::check(src, &QUERY)?;
117        Ok(Fragment::from_str_unchecked(src))
118    }
119
120    /// Returns the decoded fragment.
121    pub fn decode(&self) -> Cow<'_, str> {
122        unquote(&self.0, Component::Fragment)
123    }
124}