querystrong/lib.rs
1#![forbid(unsafe_code, future_incompatible)]
2#![deny(
3 missing_debug_implementations,
4 nonstandard_style,
5 missing_copy_implementations,
6 unused_qualifications
7)]
8
9//! # QueryStrong: A flexible interface for querystrings
10//!
11//! QueryStrong parses query strings (e.g. `user[name][first]=jacob&user[age]=100`)
12//! into a nested [`Value`] tree that can be traversed, mutated, and serialized back
13//! to a string.
14//!
15//! ```rust
16//! use querystrong::QueryStrong;
17//!
18//! let mut qs = QueryStrong::parse("user[name][first]=jacob&user[language]=rust");
19//! assert_eq!(qs["user[name][first]"], "jacob");
20//! assert_eq!(qs.get_str("user[language]"), Some("rust"));
21//! assert!(qs["user"].is_map());
22//! assert!(qs["user[name]"].is_map());
23//!
24//! qs.append("user[name][last]", "rothstein").unwrap();
25//! qs.append("user[language]", "english").unwrap();
26//! assert_eq!(
27//! qs.to_string(),
28//! "user[language][]=rust&user[language][]=english&\
29//! user[name][first]=jacob&user[name][last]=rothstein"
30//! );
31//! ```
32//!
33//! ## Permissive parsing
34//!
35//! [`QueryStrong::parse`] never fails. If a key cannot be parsed or a value
36//! conflicts with an existing entry the error is recorded internally and
37//! parsing continues. Accumulated errors are available via
38//! [`QueryStrong::errors`].
39//!
40//! Use [`QueryStrong::parse_strict`] if you need a hard failure on any error,
41//! or call [`QueryStrong::into_result`] / [`QueryStrong::unwrap`] after the fact.
42//!
43//! ## Zero-copy parsing
44//!
45//! Parsing borrows directly from the input `&str` wherever possible. String
46//! regions that do not require percent-decoding or `+`-as-space substitution
47//! are never copied. The lifetime `'a` on [`QueryStrong<'a>`] and [`Value<'a>`]
48//! tracks this borrow. Call [`QueryStrong::into_owned`] to obtain a `'static`
49//! value that owns all its strings.
50//!
51//! ## List variants
52//!
53//! Empty-bracket appends (`a[]=v`) produce a dense [`Value::List`]. Explicit
54//! numeric indices (`a[3]=v`) produce a [`Value::SparseList`] backed by a
55//! `BTreeMap`, which is memory-safe for large indices like `a[999999]=v`. A
56//! sparse list collapses back to a dense list automatically once its indices
57//! become contiguous from zero.
58
59use std::{
60 convert::{Infallible, TryFrom, TryInto},
61 fmt::{self, Debug, Display, Formatter, Write},
62 ops::{Deref, DerefMut, Index},
63 str::FromStr,
64};
65
66mod indexer;
67pub use indexer::Indexer;
68
69mod index_path;
70pub use index_path::IndexPath;
71
72mod value;
73pub use value::Value;
74
75mod error;
76pub use error::{Error, ParseErrors, ParseResult, Result};
77
78mod percent_coding;
79pub(crate) use percent_coding::{decode, encode};
80
81/// A parsed query string.
82///
83/// The lifetime `'a` is tied to the input slice supplied to [`QueryStrong::parse`].
84/// String data that does not require percent-decoding is borrowed directly from
85/// that slice without any allocation. Call [`into_owned`](QueryStrong::into_owned)
86/// to detach from the original string.
87///
88/// The top-level value is always a [`Value::Map`]. Because `QueryStrong` implements
89/// [`Deref<Target = Value>`](std::ops::Deref), all [`Value`] methods are available
90/// directly on a `QueryStrong`.
91#[derive(Clone, PartialEq, Eq)]
92pub struct QueryStrong<'a> {
93 value: Value<'a>,
94 errors: Option<ParseErrors<'a>>,
95}
96
97impl<'a> QueryStrong<'a> {
98 /// Creates a new (empty) querystrong that contains a map as the
99 /// top level value
100 pub fn new() -> Self {
101 Self {
102 value: Value::new_map(),
103 errors: None,
104 }
105 }
106
107 /// Parse a query string permissively, accumulating errors rather than failing.
108 ///
109 /// This is the recommended entry-point for user-supplied query strings.
110 /// The returned `QueryStrong` always contains the successfully-parsed portions
111 /// of the input; segments that could not be parsed are skipped and their errors
112 /// are stored internally. Retrieve them with [`errors`](QueryStrong::errors).
113 ///
114 /// String data that does not need percent-decoding is borrowed from `s`
115 /// without copying.
116 ///
117 /// ```
118 /// use querystrong::QueryStrong;
119 ///
120 /// // Well-formed input: no errors
121 /// let qs = QueryStrong::parse("a=1&b[c]=2");
122 /// assert_eq!(qs.get_str("a"), Some("1"));
123 /// assert!(qs.errors().is_none());
124 ///
125 /// // Conflicting segments are skipped; valid ones are preserved
126 /// let qs = QueryStrong::parse("a=1&a[b]=2");
127 /// assert_eq!(qs.get_str("a"), Some("1"));
128 /// assert_eq!(qs.errors().unwrap().errors().len(), 1);
129 /// ```
130 pub fn parse(s: &'a str) -> Self {
131 let mut querystrong = QueryStrong::new();
132 let mut remaining = s;
133
134 while !remaining.is_empty() {
135 let kv;
136
137 if let Some(ampersand_index) = memchr::memchr(b'&', remaining.as_bytes()) {
138 kv = &remaining[..ampersand_index];
139 remaining = &remaining[ampersand_index + 1..];
140 } else {
141 kv = remaining;
142 remaining = "";
143 }
144
145 if !kv.is_empty() {
146 let (k, v) = if let Some(equals_index) = memchr::memchr(b'=', kv.as_bytes()) {
147 (&kv[..equals_index], Some(&kv[equals_index + 1..]))
148 } else {
149 (kv, None)
150 };
151
152 if let Err(e) = IndexPath::parse(k).and_then(|k| querystrong.append(k, v)) {
153 querystrong
154 .errors
155 .get_or_insert_with(|| ParseErrors::new(s))
156 .push(e);
157 }
158 }
159 }
160
161 querystrong
162 }
163
164 /// Parse a query string, returning `Err` if any part of the input is invalid.
165 ///
166 /// Equivalent to `QueryStrong::parse(s).into_result()`. Prefer
167 /// [`parse`](QueryStrong::parse) for untrusted inputs where a best-effort
168 /// result is acceptable.
169 pub fn parse_strict(s: &'a str) -> ParseResult<'a, Self> {
170 Self::parse(s).into_result()
171 }
172
173 /// Returns accumulated parse errors, or `None` if parsing was clean.
174 ///
175 /// `None` means every key-value pair was parsed and inserted successfully.
176 /// `Some(_)` means at least one segment was skipped; the successfully-parsed
177 /// portions of the input are still accessible on `self`.
178 pub fn errors(&self) -> Option<&ParseErrors<'a>> {
179 self.errors.as_ref()
180 }
181
182 /// Convert this `QueryStrong<'a>` into a `QueryStrong<'static>` by cloning
183 /// any strings that were borrowed from the original input.
184 ///
185 /// Useful when you need to store the parsed result beyond the lifetime of
186 /// the input string.
187 pub fn into_owned(self) -> QueryStrong<'static> {
188 QueryStrong {
189 value: self.value.into_owned(),
190 errors: self.errors.map(ParseErrors::into_owned),
191 }
192 }
193
194 /// Consume `self`, returning `Ok(self)` if there were no parse errors or
195 /// `Err(ParseErrors)` if any were accumulated.
196 ///
197 /// The errors are moved out of `self` before wrapping it in `Ok`, so the
198 /// returned value has a clean error state.
199 pub fn into_result(mut self) -> ParseResult<'a, Self> {
200 match self.errors.take() {
201 Some(error) => Err(error),
202 None => Ok(self),
203 }
204 }
205
206 /// Panic if there were any parse errors; otherwise return `self`.
207 ///
208 /// Intended for tests or contexts where the input is known to be valid.
209 /// For production code prefer checking [`errors`](QueryStrong::errors)
210 /// or using [`parse_strict`](QueryStrong::parse_strict).
211 pub fn unwrap(self) -> Self {
212 self.into_result().unwrap()
213 }
214}
215
216impl FromStr for QueryStrong<'static> {
217 type Err = Infallible;
218
219 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
220 Ok(QueryStrong::parse(s).into_owned())
221 }
222}
223
224impl<'a> Default for QueryStrong<'a> {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230impl<'a> Deref for QueryStrong<'a> {
231 type Target = Value<'a>;
232
233 fn deref(&self) -> &Self::Target {
234 &self.value
235 }
236}
237
238impl<'a> DerefMut for QueryStrong<'a> {
239 fn deref_mut(&mut self) -> &mut Self::Target {
240 &mut self.value
241 }
242}
243
244impl<'a: 'b, 'b> IntoIterator for &'a QueryStrong<'b> {
245 type Item = (IndexPath<'a>, Option<String>);
246
247 type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
248
249 fn into_iter(self) -> Self::IntoIter {
250 Box::new(self.value.into_iter().filter_map(|(k, v)| match (k, v) {
251 (Some(k), Some(v)) => {
252 if k.is_empty() || k.front() == Some(&Indexer::Empty) {
253 Some((IndexPath::try_from(v).ok()?, None))
254 } else {
255 Some((k, Some(v)))
256 }
257 }
258 (Some(k), None) => Some((k, None)),
259 (None, Some(k)) => Some((Indexer::from(k).into(), None)),
260 (None, None) => None,
261 }))
262 }
263}
264
265impl<'a, 'b, K> Index<K> for QueryStrong<'b>
266where
267 K: TryInto<IndexPath<'a>>,
268{
269 type Output = Value<'b>;
270
271 fn index(&self, key: K) -> &Self::Output {
272 self.get(key).unwrap()
273 }
274}
275
276impl Display for QueryStrong<'_> {
277 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
278 let mut first = true;
279
280 for (key, value) in self {
281 if first {
282 first = false;
283 } else {
284 f.write_char('&')?;
285 }
286
287 f.write_str(&key.to_string())?;
288
289 if let Some(value) = value {
290 f.write_char('=')?;
291 f.write_str(&value)?;
292 }
293 }
294 Ok(())
295 }
296}
297
298impl Debug for QueryStrong<'_> {
299 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
300 Debug::fmt(&self.value, f)
301 }
302}
303
304impl<'a, V: Into<Value<'a>>> From<V> for QueryStrong<'a> {
305 fn from(value: V) -> Self {
306 Self {
307 value: value.into(),
308 errors: None,
309 }
310 }
311}
312
313#[cfg(feature = "serde")]
314impl serde::Serialize for QueryStrong<'_> {
315 fn serialize<S: serde::Serializer>(
316 &self,
317 serializer: S,
318 ) -> std::result::Result<S::Ok, S::Error> {
319 self.value.serialize(serializer)
320 }
321}