Skip to main content

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}