ttpkit_url/
query.rs

1//! Query string parsing and serialization.
2
3use std::{
4    borrow::{Borrow, Cow},
5    fmt::{self, Display, Formatter},
6    ops::Deref,
7    str::{FromStr, Utf8Error},
8};
9
10/// QueryDict key.
11#[derive(Debug, Clone)]
12pub struct QueryDictKey {
13    inner: Cow<'static, str>,
14}
15
16impl QueryDictKey {
17    /// Create a new key from a given static string.
18    #[inline]
19    pub const fn from_static(inner: &'static str) -> Self {
20        Self {
21            inner: Cow::Borrowed(inner),
22        }
23    }
24
25    /// Create a new key from a given string.
26    pub fn new<T>(key: T) -> Self
27    where
28        T: Into<String>,
29    {
30        Self {
31            inner: Cow::Owned(key.into()),
32        }
33    }
34}
35
36impl AsRef<str> for QueryDictKey {
37    #[inline]
38    fn as_ref(&self) -> &str {
39        &self.inner
40    }
41}
42
43impl Borrow<str> for QueryDictKey {
44    #[inline]
45    fn borrow(&self) -> &str {
46        &self.inner
47    }
48}
49
50impl Deref for QueryDictKey {
51    type Target = str;
52
53    #[inline]
54    fn deref(&self) -> &Self::Target {
55        &self.inner
56    }
57}
58
59impl PartialEq for QueryDictKey {
60    #[inline]
61    fn eq(&self, other: &Self) -> bool {
62        self.inner.eq_ignore_ascii_case(&other.inner)
63    }
64}
65
66impl Eq for QueryDictKey {}
67
68impl PartialEq<str> for QueryDictKey {
69    #[inline]
70    fn eq(&self, other: &str) -> bool {
71        self.inner.eq_ignore_ascii_case(other)
72    }
73}
74
75impl PartialEq<QueryDictKey> for str {
76    #[inline]
77    fn eq(&self, other: &QueryDictKey) -> bool {
78        self.eq_ignore_ascii_case(&other.inner)
79    }
80}
81
82impl From<&'static str> for QueryDictKey {
83    #[inline]
84    fn from(key: &'static str) -> Self {
85        Self::from_static(key)
86    }
87}
88
89impl From<String> for QueryDictKey {
90    #[inline]
91    fn from(key: String) -> Self {
92        Self::new(key)
93    }
94}
95
96/// QueryDict value.
97#[derive(Debug, Clone)]
98pub struct QueryDictValue {
99    inner: Cow<'static, str>,
100}
101
102impl QueryDictValue {
103    /// Create a new value from a given static string.
104    #[inline]
105    pub const fn from_static(inner: &'static str) -> Self {
106        Self {
107            inner: Cow::Borrowed(inner),
108        }
109    }
110
111    /// Create a new value from a given string.
112    pub fn new<T>(key: T) -> Self
113    where
114        T: Into<String>,
115    {
116        Self {
117            inner: Cow::Owned(key.into()),
118        }
119    }
120}
121
122impl AsRef<str> for QueryDictValue {
123    #[inline]
124    fn as_ref(&self) -> &str {
125        &self.inner
126    }
127}
128
129impl Borrow<str> for QueryDictValue {
130    #[inline]
131    fn borrow(&self) -> &str {
132        &self.inner
133    }
134}
135
136impl Deref for QueryDictValue {
137    type Target = str;
138
139    #[inline]
140    fn deref(&self) -> &Self::Target {
141        &self.inner
142    }
143}
144
145impl From<&'static str> for QueryDictValue {
146    #[inline]
147    fn from(key: &'static str) -> Self {
148        Self::from_static(key)
149    }
150}
151
152impl From<String> for QueryDictValue {
153    #[inline]
154    fn from(key: String) -> Self {
155        Self::new(key)
156    }
157}
158
159/// QueryDict item.
160#[derive(Debug, Clone)]
161pub struct QueryDictItem {
162    key: QueryDictKey,
163    value: Option<QueryDictValue>,
164}
165
166impl QueryDictItem {
167    /// Get the item key.
168    #[inline]
169    pub fn key(&self) -> &QueryDictKey {
170        &self.key
171    }
172
173    /// Get the item value.
174    #[inline]
175    pub fn value(&self) -> Option<&QueryDictValue> {
176        self.value.as_ref()
177    }
178}
179
180impl<K> From<(K,)> for QueryDictItem
181where
182    K: Into<QueryDictKey>,
183{
184    fn from(item: (K,)) -> Self {
185        let (key,) = item;
186
187        Self {
188            key: key.into(),
189            value: None,
190        }
191    }
192}
193
194impl<K, V> From<(K, V)> for QueryDictItem
195where
196    K: Into<QueryDictKey>,
197    V: Into<QueryDictValue>,
198{
199    fn from(item: (K, V)) -> Self {
200        let (key, value) = item;
201
202        Self {
203            key: key.into(),
204            value: Some(value.into()),
205        }
206    }
207}
208
209impl Display for QueryDictItem {
210    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
211        let key = crate::url_encode(self.key.as_ref());
212
213        if let Some(value) = self.value.as_ref() {
214            let value = crate::url_encode(value.as_ref());
215
216            write!(f, "{key}={value}")
217        } else {
218            write!(f, "{key}")
219        }
220    }
221}
222
223impl FromStr for QueryDictItem {
224    type Err = Utf8Error;
225
226    fn from_str(s: &str) -> Result<Self, Self::Err> {
227        let (key, value) = s
228            .split_once('=')
229            .map(|(k, v)| (k, Some(v)))
230            .unwrap_or((s, None));
231
232        let key = String::from_utf8(Cow::into_owned(crate::url_decode(key)))
233            .map_err(|err| err.utf8_error())?
234            .into();
235
236        let value = value
237            .map(crate::url_decode)
238            .map(|decoded| String::from_utf8(decoded.into_owned()))
239            .transpose()
240            .map_err(|err| err.utf8_error())?
241            .map(QueryDictValue::from);
242
243        let item = Self { key, value };
244
245        Ok(item)
246    }
247}
248
249/// QueryDict.
250///
251/// This type can be used to parse and serialize URL query strings.
252///
253/// The QueryDict uses `Vec<_>` internally to store the items, so it preserves
254/// the item order and allows multiple items with the same key. This also means
255/// that complexity of some operations is `O(n)`. However, this should not pose
256/// a problem in practice, as the number of query parameters is usually small.
257///  It is a trade-off between performance and footprint.
258#[derive(Clone)]
259pub struct QueryDict {
260    items: Vec<QueryDictItem>,
261}
262
263impl QueryDict {
264    /// Create a new instance of QueryDict.
265    #[inline]
266    pub const fn new() -> Self {
267        Self { items: Vec::new() }
268    }
269
270    /// Create a new instance of QueryDict with a given initial capacity.
271    #[inline]
272    pub fn with_capacity(capacity: usize) -> Self {
273        Self {
274            items: Vec::with_capacity(capacity),
275        }
276    }
277
278    /// Add a given item.
279    ///
280    /// This is an `O(1)` operation.
281    pub fn add<T>(&mut self, item: T)
282    where
283        T: Into<QueryDictItem>,
284    {
285        self.items.push(item.into());
286    }
287
288    /// Replace all items having the same name (if any).
289    ///
290    /// This is an `O(n)` operation.
291    pub fn set<T>(&mut self, item: T)
292    where
293        T: Into<QueryDictItem>,
294    {
295        // helper function preventing expensive monomorphizations
296        fn inner(items: &mut Vec<QueryDictItem>, item: QueryDictItem) {
297            items.retain(|i| !i.key.eq_ignore_ascii_case(&item.key));
298            items.push(item);
299        }
300
301        inner(&mut self.items, item.into());
302    }
303
304    /// Remove all items with a given key.
305    ///
306    /// This is an `O(n)` operation.
307    pub fn remove<N>(&mut self, key: &N)
308    where
309        N: AsRef<str> + ?Sized,
310    {
311        // helper function preventing expensive monomorphizations
312        fn inner(fields: &mut Vec<QueryDictItem>, key: &str) {
313            fields.retain(|i| !i.key.eq_ignore_ascii_case(key));
314        }
315
316        inner(&mut self.items, key.as_ref());
317    }
318
319    /// Get header fields with a given key.
320    ///
321    /// This is an `O(n)` operation.
322    pub fn get<'a, N>(&'a self, key: &'a N) -> KeyIter<'a>
323    where
324        N: AsRef<str> + ?Sized,
325    {
326        KeyIter {
327            inner: self.all(),
328            key: key.as_ref(),
329        }
330    }
331
332    /// Get the last item with a given key.
333    ///
334    /// This is an `O(n)` operation.
335    pub fn last<'a, N>(&'a self, key: &'a N) -> Option<&'a QueryDictItem>
336    where
337        N: AsRef<str> + ?Sized,
338    {
339        // helper function to avoid expensive monomorphizations
340        fn inner<'a>(iter: &mut KeyIter<'a>) -> Option<&'a QueryDictItem> {
341            iter.next_back()
342        }
343
344        inner(&mut self.get(key))
345    }
346
347    /// Get value of the last item with a given key.
348    ///
349    /// This is an `O(n)` operation.
350    pub fn last_value<'a, N>(&'a self, key: &'a N) -> Option<&'a QueryDictValue>
351    where
352        N: AsRef<str> + ?Sized,
353    {
354        // helper function to avoid expensive monomorphizations
355        fn inner<'a>(iter: &mut KeyIter<'a>) -> Option<&'a QueryDictValue> {
356            iter.next_back().and_then(|item| item.value())
357        }
358
359        inner(&mut self.get(key))
360    }
361
362    /// Get all items.
363    #[inline]
364    pub fn all(&self) -> Iter<'_> {
365        Iter {
366            inner: self.items.iter(),
367        }
368    }
369
370    /// Check if the collection is empty.
371    #[inline]
372    pub fn is_empty(&self) -> bool {
373        self.items.is_empty()
374    }
375
376    /// Get the number of items in the collection.
377    #[inline]
378    pub fn len(&self) -> usize {
379        self.items.len()
380    }
381}
382
383impl Default for QueryDict {
384    #[inline]
385    fn default() -> Self {
386        Self::new()
387    }
388}
389
390impl Display for QueryDict {
391    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
392        let mut iter = self.items.iter();
393
394        if let Some(item) = iter.next() {
395            write!(f, "{item}")?;
396        }
397
398        for item in iter {
399            write!(f, "&{item}")?;
400        }
401
402        Ok(())
403    }
404}
405
406impl From<Vec<QueryDictItem>> for QueryDict {
407    #[inline]
408    fn from(items: Vec<QueryDictItem>) -> Self {
409        Self { items }
410    }
411}
412
413impl FromStr for QueryDict {
414    type Err = Utf8Error;
415
416    fn from_str(s: &str) -> Result<Self, Self::Err> {
417        let mut res = Self::new();
418
419        for item in s.split('&') {
420            let item = item.trim();
421
422            if item.is_empty() {
423                continue;
424            }
425
426            res.add(QueryDictItem::from_str(item)?);
427        }
428
429        Ok(res)
430    }
431}
432
433/// QueryDict item iterator.
434pub struct Iter<'a> {
435    inner: std::slice::Iter<'a, QueryDictItem>,
436}
437
438impl<'a> Iterator for Iter<'a> {
439    type Item = &'a QueryDictItem;
440
441    #[inline]
442    fn next(&mut self) -> Option<Self::Item> {
443        self.inner.next()
444    }
445}
446
447impl<'a> DoubleEndedIterator for Iter<'a> {
448    #[inline]
449    fn next_back(&mut self) -> Option<Self::Item> {
450        self.inner.next_back()
451    }
452}
453
454impl<'a> ExactSizeIterator for Iter<'a> {
455    #[inline]
456    fn len(&self) -> usize {
457        self.inner.len()
458    }
459}
460
461/// QueryDict item iterator.
462pub struct KeyIter<'a> {
463    inner: Iter<'a>,
464    key: &'a str,
465}
466
467impl<'a> Iterator for KeyIter<'a> {
468    type Item = &'a QueryDictItem;
469
470    fn next(&mut self) -> Option<Self::Item> {
471        #[allow(clippy::while_let_on_iterator)]
472        while let Some(item) = self.inner.next() {
473            if item.key.eq_ignore_ascii_case(self.key) {
474                return Some(item);
475            }
476        }
477
478        None
479    }
480}
481
482impl<'a> DoubleEndedIterator for KeyIter<'a> {
483    fn next_back(&mut self) -> Option<Self::Item> {
484        while let Some(item) = self.inner.next_back() {
485            if item.key.eq_ignore_ascii_case(self.key) {
486                return Some(item);
487            }
488        }
489
490        None
491    }
492}
493
494#[cfg(test)]
495mod tests {
496    use std::{borrow::Borrow, str::FromStr};
497
498    use super::{QueryDict, QueryDictValue};
499
500    fn qd_value_eq<A, B>(a: A, b: B) -> bool
501    where
502        A: Borrow<QueryDictValue>,
503        B: Borrow<QueryDictValue>,
504    {
505        let a = a.borrow();
506        let b = b.borrow();
507
508        a.as_ref() == b.as_ref()
509    }
510
511    fn qd_values_eq<A, B>(a: A, b: B) -> bool
512    where
513        A: AsRef<[QueryDictValue]>,
514        B: AsRef<[QueryDictValue]>,
515    {
516        let a = a.as_ref();
517        let b = b.as_ref();
518
519        a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| qd_value_eq(a, b))
520    }
521
522    #[test]
523    fn test_query_dict_from_string() {
524        let inputs = &[
525            "sss",
526            "aa=bb",
527            "aa=bb&cc=dd",
528            "aa=bb&aa=cc",
529            "email=some%40example%2Ecom",
530        ];
531
532        for item in inputs {
533            let dict = QueryDict::from_str(item);
534
535            assert!(dict.is_ok());
536
537            assert_eq!(format!("{}", dict.unwrap()), item.to_string());
538        }
539    }
540
541    #[test]
542    fn test_multiple_values() {
543        let mut dict = QueryDict::default();
544
545        dict.add(("slot", "first"));
546        dict.add(("slot", "second"));
547
548        assert_eq!(dict.last_value("slot").map(|v| v.as_ref()), Some("second"));
549
550        assert!(qd_values_eq(
551            dict.get("slot")
552                .filter_map(|e| e.value())
553                .cloned()
554                .collect::<Vec<_>>(),
555            vec![
556                QueryDictValue::from("first"),
557                QueryDictValue::from("second")
558            ]
559        ));
560    }
561
562    #[test]
563    fn test_multiple_values_from_string() {
564        let dict = QueryDict::from_str("bb[]=1&bb[]=2&&cc[]=3&bb[]=4").unwrap();
565
566        assert!(qd_values_eq(
567            dict.get("bb[]")
568                .filter_map(|e| e.value())
569                .cloned()
570                .collect::<Vec<_>>(),
571            vec![
572                QueryDictValue::from("1"),
573                QueryDictValue::from("2"),
574                QueryDictValue::from("4"),
575            ]
576        ));
577    }
578
579    #[test]
580    fn test_percent_decoding() {
581        let dict = QueryDict::from_str("email=some%40example%2Ecom").unwrap();
582
583        assert_eq!(
584            dict.last_value("email").map(|v| v.as_ref()),
585            Some("some@example.com")
586        );
587    }
588
589    #[test]
590    fn test_percent_encoding() {
591        let mut dict = QueryDict::new();
592
593        dict.add(("email", "some@example.com"));
594
595        assert_eq!(dict.to_string(), String::from("email=some%40example%2Ecom"));
596    }
597
598    #[test]
599    fn test_space_in_query() {
600        let dict = QueryDict::from_str("a=%20x%20z%20&b=y").unwrap();
601
602        assert_eq!(dict.last_value("a").map(|v| v.as_ref()), Some(" x z "));
603    }
604
605    #[test]
606    fn test_remove() {
607        let mut dict = QueryDict::from_str("bb[]=1&bb[]=2&cc[]=3&bb[]=4").unwrap();
608
609        assert_eq!(dict.get("bb[]").count(), 3);
610        assert_eq!(dict.all().count(), 4);
611
612        dict.remove("bb[]");
613
614        assert_eq!(dict.get("bb[]").count(), 0);
615        assert_eq!(dict.all().count(), 1);
616    }
617
618    #[test]
619    fn test_set() {
620        let mut dict = QueryDict::from_str("bb[]=1&bb[]=2&cc[]=3&bb[]=4").unwrap();
621
622        assert_eq!(dict.get("bb[]").count(), 3);
623        assert_eq!(dict.all().count(), 4);
624
625        dict.set(("bb[]", "5"));
626
627        assert_eq!(dict.get("bb[]").count(), 1);
628        assert_eq!(dict.all().count(), 2);
629    }
630}