Skip to main content

xocomil/
query.rs

1//! Iterator over `application/x-www-form-urlencoded` name/value pairs.
2//!
3//! Yields raw, **undecoded** byte slices — pass each one through
4//! [`pct::decode`](crate::pct::decode) with [`Mode::Form`](crate::pct::Mode::Form)
5//! to apply percent-decoding (and `+` → space) into a caller-provided
6//! buffer. This keeps the iterator zero-allocation: every item still
7//! borrows from the original input.
8//!
9//! Matches the behavior of WHATWG URL form decoding for splitting:
10//! pairs separated by `&`, names and values by the first `=`. A pair
11//! with no `=` yields the entire pair as the name with an empty value.
12//! Empty pairs (e.g. `&&`) are skipped.
13//!
14//! # Example
15//!
16//! ```
17//! use xocomil::query;
18//!
19//! let pairs: Vec<_> = query::parse(b"a=1&b=hello+world&c").collect();
20//! assert_eq!(pairs, &[
21//!     (&b"a"[..], &b"1"[..]),
22//!     (&b"b"[..], &b"hello+world"[..]),
23//!     (&b"c"[..], &b""[..]),
24//! ]);
25//! ```
26
27/// Iterator over `(name, value)` byte slices in a query string.
28///
29/// Both `name` and `value` are returned **raw** — still percent-encoded,
30/// with `+` not yet converted to space. Call
31/// [`pct::decode`](crate::pct::decode) with [`Mode::Form`](crate::pct::Mode::Form)
32/// to decode either component.
33#[derive(Clone, Debug)]
34pub struct QueryIter<'a> {
35    rest: &'a [u8],
36}
37
38impl<'a> Iterator for QueryIter<'a> {
39    type Item = (&'a [u8], &'a [u8]);
40
41    fn next(&mut self) -> Option<Self::Item> {
42        // Skip leading empty segments (e.g. `&&a=1` -> ("a", "1")).
43        loop {
44            if self.rest.is_empty() {
45                return None;
46            }
47            if self.rest[0] == b'&' {
48                self.rest = &self.rest[1..];
49                continue;
50            }
51            break;
52        }
53
54        let (segment, tail) = self.rest.iter().position(|&b| b == b'&').map_or_else(
55            || (self.rest, &b""[..]),
56            |amp| (&self.rest[..amp], &self.rest[amp + 1..]),
57        );
58        self.rest = tail;
59
60        let (name, value) = segment.iter().position(|&b| b == b'=').map_or_else(
61            || (segment, &b""[..]),
62            |eq| (&segment[..eq], &segment[eq + 1..]),
63        );
64        Some((name, value))
65    }
66}
67
68/// Parse a query string into an iterator of `(name, value)` pairs.
69///
70/// Pass the raw query component (the bytes after `?`, without the `?`).
71/// See [`QueryIter`] for behavior details.
72#[inline]
73#[must_use]
74pub const fn parse(query: &[u8]) -> QueryIter<'_> {
75    QueryIter { rest: query }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    fn collect(input: &[u8]) -> Vec<(&[u8], &[u8])> {
83        parse(input).collect()
84    }
85
86    #[test]
87    fn empty_input() {
88        assert!(collect(b"").is_empty());
89    }
90
91    #[test]
92    fn single_pair() {
93        assert_eq!(collect(b"a=1"), &[(&b"a"[..], &b"1"[..])]);
94    }
95
96    #[test]
97    fn multiple_pairs() {
98        assert_eq!(
99            collect(b"a=1&b=2&c=3"),
100            &[
101                (&b"a"[..], &b"1"[..]),
102                (&b"b"[..], &b"2"[..]),
103                (&b"c"[..], &b"3"[..]),
104            ]
105        );
106    }
107
108    #[test]
109    fn name_without_value() {
110        assert_eq!(collect(b"flag"), &[(&b"flag"[..], &b""[..])]);
111        assert_eq!(
112            collect(b"a=1&flag&b=2"),
113            &[
114                (&b"a"[..], &b"1"[..]),
115                (&b"flag"[..], &b""[..]),
116                (&b"b"[..], &b"2"[..]),
117            ]
118        );
119    }
120
121    #[test]
122    fn empty_value() {
123        assert_eq!(collect(b"a="), &[(&b"a"[..], &b""[..])]);
124    }
125
126    #[test]
127    fn empty_name() {
128        assert_eq!(collect(b"=v"), &[(&b""[..], &b"v"[..])]);
129    }
130
131    #[test]
132    fn skip_empty_segments() {
133        assert_eq!(collect(b"&&a=1"), &[(&b"a"[..], &b"1"[..])]);
134        assert_eq!(
135            collect(b"a=1&&b=2"),
136            &[(&b"a"[..], &b"1"[..]), (&b"b"[..], &b"2"[..])]
137        );
138        assert!(collect(b"&&&").is_empty());
139    }
140
141    #[test]
142    fn value_contains_equals() {
143        // Only the first `=` splits name from value.
144        assert_eq!(collect(b"a=1=2"), &[(&b"a"[..], &b"1=2"[..])]);
145    }
146
147    #[test]
148    fn raw_undecoded_output() {
149        // `+` and `%XX` are left raw — caller decodes per-pair.
150        assert_eq!(
151            collect(b"q=hello+world&n=foo%20bar"),
152            &[
153                (&b"q"[..], &b"hello+world"[..]),
154                (&b"n"[..], &b"foo%20bar"[..]),
155            ]
156        );
157    }
158
159    #[test]
160    fn iterator_is_lazy_and_clone() {
161        let iter = parse(b"a=1&b=2");
162        let mut a = iter.clone();
163        let mut b = iter;
164        assert_eq!(a.next(), Some((&b"a"[..], &b"1"[..])));
165        assert_eq!(b.next(), Some((&b"a"[..], &b"1"[..])));
166    }
167
168    #[test]
169    fn integrates_with_pct_form_decode() {
170        use crate::pct::{Mode, decode};
171
172        let pairs: Vec<_> = parse(b"q=hello+world").collect();
173        let (name, value) = pairs[0];
174        let mut nbuf = [0u8; 16];
175        let mut vbuf = [0u8; 16];
176        assert_eq!(decode(name, Mode::Form, &mut nbuf).unwrap(), b"q");
177        assert_eq!(
178            decode(value, Mode::Form, &mut vbuf).unwrap(),
179            b"hello world"
180        );
181    }
182}