Skip to main content

spikard_http/
query_parser.rs

1//! Fast query string parser
2//!
3//! Vendored and adapted from https://github.com/litestar-org/fast-query-parsers
4//! Original author: Naaman Hirschfeld (same author as Spikard)
5//!
6//! This parser handles multiple values for the same key and auto-converts types.
7
8use lazy_static::lazy_static;
9use regex::Regex;
10use rustc_hash::FxHashMap;
11use serde_json::{Value, from_str};
12use std::borrow::Cow;
13use std::convert::Infallible;
14
15lazy_static! {
16    static ref PARENTHESES_RE: Regex = Regex::new(r"(^\[.*\]$|^\{.*\}$)").unwrap();
17}
18
19/// URL-decode a byte slice, replacing '+' with space and handling percent-encoding.
20///
21/// Optimized to avoid intermediate allocations by:
22/// - Processing bytes directly without intermediate String conversion
23/// - Using Cow to avoid allocation when no encoding is present
24/// - Replacing '+' during decoding rather than as a separate pass
25#[inline]
26fn url_decode_optimized(input: &[u8]) -> Cow<'_, str> {
27    let has_encoded = input.iter().any(|&b| b == b'+' || b == b'%');
28
29    if !has_encoded {
30        return match std::str::from_utf8(input) {
31            Ok(s) => Cow::Borrowed(s),
32            Err(_) => Cow::Owned(String::from_utf8_lossy(input).into_owned()),
33        };
34    }
35
36    let mut result = Vec::with_capacity(input.len());
37    let mut i = 0;
38
39    while i < input.len() {
40        match input[i] {
41            b'+' => {
42                result.push(b' ');
43                i += 1;
44            }
45            b'%' if i + 2 < input.len() => {
46                if let (Some(hi), Some(lo)) = (
47                    char::from(input[i + 1]).to_digit(16),
48                    char::from(input[i + 2]).to_digit(16),
49                ) {
50                    result.push((hi * 16 + lo) as u8);
51                    i += 3;
52                } else {
53                    result.push(input[i]);
54                    i += 1;
55                }
56            }
57            b => {
58                result.push(b);
59                i += 1;
60            }
61        }
62    }
63
64    Cow::Owned(String::from_utf8_lossy(&result).into_owned())
65}
66
67/// Parse a query string into a vector of (key, value) tuples.
68///
69/// Handles URL encoding and supports multiple values for the same key.
70///
71/// # Arguments
72/// * `qs` - The query string bytes
73/// * `separator` - The separator character (typically '&')
74///
75/// # Example
76/// ```ignore
77/// let result = parse_query_string(b"foo=1&foo=2&bar=test", '&');
78/// // vec![("foo", "1"), ("foo", "2"), ("bar", "test")]
79/// ```
80///
81/// # Performance
82/// Optimized to minimize allocations by:
83/// - Processing bytes directly without intermediate String allocation
84/// - Using custom URL decoder that handles '+' replacement in one pass
85/// - Pre-allocating result vector
86#[inline]
87pub fn parse_query_string(qs: &[u8], separator: char) -> Vec<(String, String)> {
88    if qs.is_empty() {
89        return Vec::new();
90    }
91
92    let separator_byte = separator as u8;
93    let mut result = Vec::with_capacity(8);
94
95    let mut start = 0;
96    let mut i = 0;
97
98    while i <= qs.len() {
99        if i == qs.len() || qs[i] == separator_byte {
100            if i > start {
101                let pair = &qs[start..i];
102
103                if let Some(eq_pos) = pair.iter().position(|&b| b == b'=') {
104                    let key = url_decode_optimized(&pair[..eq_pos]);
105                    let value = url_decode_optimized(&pair[eq_pos + 1..]);
106                    result.push((key.into_owned(), value.into_owned()));
107                } else {
108                    let key = url_decode_optimized(pair);
109                    result.push((key.into_owned(), String::new()));
110                }
111            }
112
113            start = i + 1;
114        }
115
116        i += 1;
117    }
118
119    result
120}
121
122/// Decode a string value into a JSON Value with type conversion.
123///
124/// Handles:
125/// - JSON objects and arrays (if wrapped in brackets)
126/// - Booleans (true/false/1/0, case-insensitive)
127/// - Null
128/// - Numbers (if parse_numbers is true)
129/// - Strings (fallback)
130#[inline]
131fn decode_value(raw: &str, parse_numbers: bool) -> Value {
132    if PARENTHESES_RE.is_match(raw) {
133        let result: Value = match from_str(raw) {
134            Ok(value) => value,
135            Err(_) => match from_str(raw.replace('\'', "\"").as_str()) {
136                Ok(normalized) => normalized,
137                Err(_) => Value::Null,
138            },
139        };
140        return result;
141    }
142
143    let normalized = if raw.as_bytes().contains(&b'"') {
144        Cow::Owned(raw.replace('"', ""))
145    } else {
146        Cow::Borrowed(raw)
147    };
148
149    let json_boolean = parse_boolean(&normalized);
150    let json_null = Ok::<_, Infallible>(normalized.as_ref() == "null");
151
152    if parse_numbers {
153        let json_integer = normalized.as_ref().parse::<i64>();
154        let json_float = normalized.as_ref().parse::<f64>();
155        return match (json_integer, json_float, json_boolean, json_null) {
156            (Ok(json_integer), _, _, _) => Value::from(json_integer),
157            (_, Ok(json_float), _, _) => Value::from(json_float),
158            (_, _, Ok(json_boolean), _) => Value::from(json_boolean),
159            (_, _, _, Ok(true)) => Value::Null,
160            _ => Value::from(normalized.as_ref()),
161        };
162    }
163
164    match (json_boolean, json_null) {
165        (Ok(json_boolean), _) => Value::from(json_boolean),
166        (_, Ok(true)) => Value::Null,
167        _ => Value::from(normalized.as_ref()),
168    }
169}
170
171/// Parse a boolean value from a string.
172///
173/// Accepts:
174/// - "true" (case-insensitive) → true
175/// - "false" (case-insensitive) → false
176/// - "1" → true
177/// - "0" → false
178/// - "" (empty string) → Err (don't coerce, preserve as empty string)
179#[inline]
180fn parse_boolean(s: &str) -> Result<bool, ()> {
181    if s.eq_ignore_ascii_case("true") || s == "1" {
182        Ok(true)
183    } else if s.eq_ignore_ascii_case("false") || s == "0" {
184        Ok(false)
185    } else {
186        Err(())
187    }
188}
189
190/// Convert already-decoded query pairs into a JSON Value.
191///
192/// This is useful when callers need both:
193/// - the raw decoded pairs (for error messages / multi-value handling), and
194/// - a JSON object with type coercion (for downstream consumers),
195///
196/// while avoiding a second URL-decoding pass.
197#[inline]
198pub fn parse_query_pairs_to_json(pairs: &[(String, String)], parse_numbers: bool) -> Value {
199    let mut array_map: FxHashMap<String, Vec<Value>> = FxHashMap::default();
200
201    for (key, value) in pairs {
202        match array_map.get_mut(key) {
203            Some(entry) => {
204                entry.push(decode_value(value, parse_numbers));
205            }
206            None => {
207                array_map.insert(key.clone(), vec![decode_value(value, parse_numbers)]);
208            }
209        }
210    }
211
212    array_map
213        .iter()
214        .map(|(key, value)| {
215            if value.len() == 1 {
216                (key, value[0].to_owned())
217            } else {
218                (key, Value::Array(value.to_owned()))
219            }
220        })
221        .collect::<Value>()
222}
223
224/// Parse a query string into a JSON Value.
225///
226/// This function:
227/// - Handles multiple values for the same key (creates arrays)
228/// - Auto-converts types (numbers, booleans, null, objects, arrays)
229/// - Collapses single-item arrays into single values
230///
231/// # Arguments
232/// * `qs` - The query string bytes
233/// * `parse_numbers` - Whether to parse numeric strings into numbers
234///
235/// # Example
236/// ```ignore
237/// let result = parse_query_string_to_json(b"foo=1&foo=2&bar=test&active=true", true);
238/// // {"foo": [1, 2], "bar": "test", "active": true}
239/// ```
240#[inline]
241pub fn parse_query_string_to_json(qs: &[u8], parse_numbers: bool) -> Value {
242    let pairs = parse_query_string(qs, '&');
243    parse_query_pairs_to_json(&pairs, parse_numbers)
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use serde_json::{json, to_string};
250
251    fn eq_str(value: Value, string: &str) {
252        assert_eq!(&to_string(&value).unwrap_or_default(), string)
253    }
254
255    #[test]
256    fn test_ampersand_separator() {
257        assert_eq!(
258            parse_query_string(b"key=1&key=2&anotherKey=a&yetAnother=z", '&'),
259            vec![
260                (String::from("key"), String::from("1")),
261                (String::from("key"), String::from("2")),
262                (String::from("anotherKey"), String::from("a")),
263                (String::from("yetAnother"), String::from("z")),
264            ]
265        );
266    }
267
268    #[test]
269    fn test_handles_url_encoded_ampersand() {
270        assert_eq!(
271            parse_query_string(b"first=%26%40A.ac&second=aaa", '&'),
272            vec![
273                (String::from("first"), String::from("&@A.ac")),
274                (String::from("second"), String::from("aaa")),
275            ]
276        );
277    }
278
279    #[test]
280    fn parse_query_string_to_json_parses_simple_string() {
281        eq_str(parse_query_string_to_json(b"0=foo", true), r#"{"0":"foo"}"#);
282    }
283
284    #[test]
285    fn parse_query_string_to_json_parses_numbers() {
286        assert_eq!(parse_query_string_to_json(b"a=1", true), json!({"a": 1}));
287        assert_eq!(parse_query_string_to_json(b"a=1.1", true), json!({"a": 1.1}));
288    }
289
290    #[test]
291    fn parse_query_string_to_json_parses_booleans() {
292        assert_eq!(parse_query_string_to_json(b"a=true", false), json!({"a": true}));
293        assert_eq!(parse_query_string_to_json(b"a=false", false), json!({"a": false}));
294    }
295
296    #[test]
297    fn parse_query_string_to_json_parses_booleans_from_numbers() {
298        assert_eq!(parse_query_string_to_json(b"a=1", false), json!({"a": true}));
299        assert_eq!(parse_query_string_to_json(b"a=0", false), json!({"a": false}));
300    }
301
302    #[test]
303    fn parse_query_string_to_json_parses_case_insensitive_booleans() {
304        assert_eq!(parse_query_string_to_json(b"a=True", false), json!({"a": true}));
305        assert_eq!(parse_query_string_to_json(b"a=TRUE", false), json!({"a": true}));
306        assert_eq!(parse_query_string_to_json(b"a=False", false), json!({"a": false}));
307        assert_eq!(parse_query_string_to_json(b"a=FALSE", false), json!({"a": false}));
308    }
309
310    #[test]
311    fn parse_query_string_to_json_parses_multiple_values() {
312        assert_eq!(
313            parse_query_string_to_json(b"a=1&a=2&a=3", true),
314            json!({ "a": [1,2,3] })
315        );
316    }
317
318    #[test]
319    fn parse_query_string_to_json_parses_null() {
320        assert_eq!(parse_query_string_to_json(b"a=null", true), json!({ "a": null }));
321    }
322
323    #[test]
324    fn parse_query_string_to_json_parses_empty_string() {
325        assert_eq!(parse_query_string_to_json(b"a=", true), json!({ "a": "" }));
326    }
327
328    #[test]
329    fn parse_query_string_to_json_parses_empty_string_without_number_parsing() {
330        assert_eq!(parse_query_string_to_json(b"a=", false), json!({ "a": "" }));
331    }
332
333    #[test]
334    fn parse_query_string_to_json_parses_multiple_string_values() {
335        assert_eq!(
336            parse_query_string_to_json(b"q=foo&q=bar", true),
337            json!({ "q": ["foo", "bar"] })
338        );
339    }
340
341    #[test]
342    fn parse_query_string_to_json_parses_multiple_string_values_with_parse_numbers_false() {
343        assert_eq!(
344            parse_query_string_to_json(b"q=foo&q=bar", false),
345            json!({ "q": ["foo", "bar"] })
346        );
347    }
348
349    #[test]
350    fn parse_query_string_to_json_preserves_order_and_duplicates() {
351        assert_eq!(
352            parse_query_string_to_json(b"q=foo&q=bar&q=baz", true),
353            json!({ "q": ["foo", "bar", "baz"] })
354        );
355
356        assert_eq!(
357            parse_query_string_to_json(b"q=foo&q=foo&q=bar", true),
358            json!({ "q": ["foo", "foo", "bar"] })
359        );
360    }
361
362    #[test]
363    fn test_url_encoded_special_chars_in_values() {
364        let result = parse_query_string_to_json(b"email=x%40test.com&special=%26%40A.ac", false);
365        assert_eq!(
366            result,
367            json!({
368                "email": "x@test.com",
369                "special": "&@A.ac"
370            })
371        );
372    }
373
374    #[test]
375    fn test_url_encoded_space() {
376        let result = parse_query_string_to_json(b"name=hello%20world", false);
377        assert_eq!(result, json!({ "name": "hello world" }));
378    }
379
380    #[test]
381    fn test_url_encoded_complex_chars() {
382        let result = parse_query_string_to_json(b"name=test%26value%3D123", false);
383        assert_eq!(result, json!({ "name": "test&value=123" }));
384    }
385
386    #[test]
387    fn test_malformed_percent_single_char() {
388        let result = parse_query_string(b"key=%", '&');
389        assert_eq!(result, vec![(String::from("key"), String::from("%"))]);
390    }
391
392    #[test]
393    fn test_malformed_percent_single_hex_only() {
394        let result = parse_query_string(b"key=%2", '&');
395        assert_eq!(result, vec![(String::from("key"), String::from("%2"))]);
396    }
397
398    #[test]
399    fn test_malformed_percent_invalid_hex_chars() {
400        let result = parse_query_string(b"key=%GG&other=value", '&');
401        assert_eq!(
402            result,
403            vec![
404                (String::from("key"), String::from("%GG")),
405                (String::from("other"), String::from("value")),
406            ]
407        );
408    }
409
410    #[test]
411    fn test_malformed_percent_mixed_invalid_hex() {
412        let result = parse_query_string(b"key=%2G&other=value", '&');
413        assert_eq!(
414            result,
415            vec![
416                (String::from("key"), String::from("%2G")),
417                (String::from("other"), String::from("value")),
418            ]
419        );
420    }
421
422    #[test]
423    fn test_percent_encoding_lowercase_hex() {
424        let result = parse_query_string(b"key=%2f&other=test", '&');
425        assert_eq!(
426            result,
427            vec![
428                (String::from("key"), String::from("/")),
429                (String::from("other"), String::from("test")),
430            ]
431        );
432    }
433
434    #[test]
435    fn test_percent_encoding_uppercase_hex() {
436        let result = parse_query_string(b"key=%2F&other=test", '&');
437        assert_eq!(
438            result,
439            vec![
440                (String::from("key"), String::from("/")),
441                (String::from("other"), String::from("test")),
442            ]
443        );
444    }
445
446    #[test]
447    fn test_percent_encoding_mixed_case_hex() {
448        let result = parse_query_string(b"key=%2f%3D%4A", '&');
449        assert_eq!(result, vec![(String::from("key"), String::from("/=J"))]);
450    }
451
452    #[test]
453    fn test_plus_as_space_in_value() {
454        let result = parse_query_string(b"message=hello+world", '&');
455        assert_eq!(result, vec![(String::from("message"), String::from("hello world"))]);
456    }
457
458    #[test]
459    fn test_plus_as_space_multiple_plus() {
460        let result = parse_query_string(b"message=a+b+c+d", '&');
461        assert_eq!(result, vec![(String::from("message"), String::from("a b c d"))]);
462    }
463
464    #[test]
465    fn test_percent_encoded_space_vs_plus() {
466        let result = parse_query_string(b"a=%20space&b=+plus", '&');
467        assert_eq!(
468            result,
469            vec![
470                (String::from("a"), String::from(" space")),
471                (String::from("b"), String::from(" plus")),
472            ]
473        );
474    }
475
476    #[test]
477    fn test_mixed_plus_and_percent_encoded_space() {
478        let result = parse_query_string(b"text=hello+%20world", '&');
479        assert_eq!(result, vec![(String::from("text"), String::from("hello  world"))]);
480    }
481
482    #[test]
483    fn test_ampersand_in_value_encoded() {
484        let result = parse_query_string(b"text=foo%26bar", '&');
485        assert_eq!(result, vec![(String::from("text"), String::from("foo&bar"))]);
486    }
487
488    #[test]
489    fn test_equals_in_value_encoded() {
490        let result = parse_query_string(b"text=a%3Db", '&');
491        assert_eq!(result, vec![(String::from("text"), String::from("a=b"))]);
492    }
493
494    #[test]
495    fn test_question_mark_in_value_encoded() {
496        let result = parse_query_string(b"text=what%3F", '&');
497        assert_eq!(result, vec![(String::from("text"), String::from("what?"))]);
498    }
499
500    #[test]
501    fn test_hash_in_value_encoded() {
502        let result = parse_query_string(b"text=anchor%23top", '&');
503        assert_eq!(result, vec![(String::from("text"), String::from("anchor#top"))]);
504    }
505
506    #[test]
507    fn test_multiple_encoded_special_chars() {
508        let result = parse_query_string(b"text=%26%3D%3F%23", '&');
509        assert_eq!(result, vec![(String::from("text"), String::from("&=?#"))]);
510    }
511
512    #[test]
513    fn test_empty_query_string() {
514        let result = parse_query_string(b"", '&');
515        assert_eq!(result, vec![]);
516    }
517
518    #[test]
519    fn test_multiple_consecutive_separators() {
520        let result = parse_query_string(b"a=1&&&b=2", '&');
521        assert_eq!(
522            result,
523            vec![
524                (String::from("a"), String::from("1")),
525                (String::from("b"), String::from("2")),
526            ]
527        );
528    }
529
530    #[test]
531    fn test_key_without_value() {
532        let result = parse_query_string(b"key=", '&');
533        assert_eq!(result, vec![(String::from("key"), String::from(""))]);
534    }
535
536    #[test]
537    fn test_key_without_equals() {
538        let result = parse_query_string(b"key", '&');
539        assert_eq!(result, vec![(String::from("key"), String::from(""))]);
540    }
541
542    #[test]
543    fn test_value_without_key() {
544        let result = parse_query_string(b"=value", '&');
545        assert_eq!(result, vec![(String::from(""), String::from("value"))]);
546    }
547
548    #[test]
549    fn test_multiple_equals_in_pair() {
550        let result = parse_query_string(b"key=val=more", '&');
551        assert_eq!(result, vec![(String::from("key"), String::from("val=more"))]);
552    }
553
554    #[test]
555    fn test_separator_at_start() {
556        let result = parse_query_string(b"&key=value", '&');
557        assert_eq!(result, vec![(String::from("key"), String::from("value"))]);
558    }
559
560    #[test]
561    fn test_separator_at_end() {
562        let result = parse_query_string(b"key=value&", '&');
563        assert_eq!(result, vec![(String::from("key"), String::from("value"))]);
564    }
565
566    #[test]
567    fn test_separator_at_both_ends() {
568        let result = parse_query_string(b"&key=value&", '&');
569        assert_eq!(result, vec![(String::from("key"), String::from("value"))]);
570    }
571
572    #[test]
573    fn test_multiple_values_same_key() {
574        let result = parse_query_string(b"tag=foo&tag=bar&tag=baz", '&');
575        assert_eq!(
576            result,
577            vec![
578                (String::from("tag"), String::from("foo")),
579                (String::from("tag"), String::from("bar")),
580                (String::from("tag"), String::from("baz")),
581            ]
582        );
583    }
584
585    #[test]
586    fn test_multiple_values_mixed_keys() {
587        let result = parse_query_string(b"tag=foo&id=1&tag=bar&id=2", '&');
588        assert_eq!(
589            result,
590            vec![
591                (String::from("tag"), String::from("foo")),
592                (String::from("id"), String::from("1")),
593                (String::from("tag"), String::from("bar")),
594                (String::from("id"), String::from("2")),
595            ]
596        );
597    }
598
599    #[test]
600    fn test_json_conversion_empty_key() {
601        let result = parse_query_string_to_json(b"=value", false);
602        assert_eq!(result, json!({ "": "value" }));
603    }
604
605    #[test]
606    fn test_json_conversion_all_empty_values() {
607        let result = parse_query_string_to_json(b"a=&b=&c=", false);
608        assert_eq!(result, json!({ "a": "", "b": "", "c": "" }));
609    }
610
611    #[test]
612    fn test_json_conversion_malformed_json_object() {
613        let result = parse_query_string_to_json(b"data={invalid", false);
614        assert_eq!(result, json!({ "data": "{invalid" }));
615    }
616
617    #[test]
618    fn test_json_conversion_malformed_json_array() {
619        let result = parse_query_string_to_json(b"items=[1,2,", false);
620        assert_eq!(result, json!({ "items": "[1,2," }));
621    }
622
623    #[test]
624    fn test_json_conversion_with_quotes_in_value() {
625        let result = parse_query_string_to_json(b"text=\"hello\"", false);
626        assert_eq!(result, json!({ "text": "hello" }));
627    }
628
629    #[test]
630    fn test_json_conversion_single_quotes_in_object() {
631        let result = parse_query_string_to_json(b"obj={'key':'value'}", false);
632        let value = result.get("obj");
633        assert!(value.is_some());
634    }
635
636    #[test]
637    fn test_boolean_case_insensitive_variations() {
638        assert_eq!(parse_query_string_to_json(b"a=tRuE", false), json!({"a": true}));
639        assert_eq!(parse_query_string_to_json(b"a=FaLsE", false), json!({"a": false}));
640        assert_eq!(
641            parse_query_string_to_json(b"a=tRuE&b=FaLsE", false),
642            json!({"a": true, "b": false})
643        );
644    }
645
646    #[test]
647    fn test_boolean_with_numbers_no_parse() {
648        assert_eq!(parse_query_string_to_json(b"a=1", false), json!({"a": true}));
649        assert_eq!(parse_query_string_to_json(b"a=0", false), json!({"a": false}));
650    }
651
652    #[test]
653    fn test_number_parsing_negative() {
654        assert_eq!(parse_query_string_to_json(b"a=-123", true), json!({"a": -123}));
655        assert_eq!(parse_query_string_to_json(b"a=-1.5", true), json!({"a": -1.5}));
656    }
657
658    #[test]
659    fn test_number_parsing_zero() {
660        assert_eq!(parse_query_string_to_json(b"a=0", true), json!({"a": 0}));
661        assert_eq!(parse_query_string_to_json(b"a=0.0", true), json!({"a": 0.0}));
662    }
663
664    #[test]
665    fn test_number_parsing_scientific_notation() {
666        assert_eq!(parse_query_string_to_json(b"a=1e10", true), json!({"a": 1e10}));
667        assert_eq!(parse_query_string_to_json(b"a=1.5e-3", true), json!({"a": 1.5e-3}));
668    }
669
670    #[test]
671    fn test_array_mixed_types_with_number_parsing() {
672        assert_eq!(
673            parse_query_string_to_json(b"vals=1&vals=2.5&vals=true&vals=test", true),
674            json!({"vals": [1, 2.5, true, "test"]})
675        );
676    }
677
678    #[test]
679    fn test_array_mixed_types_without_number_parsing() {
680        assert_eq!(
681            parse_query_string_to_json(b"vals=1&vals=2.5&vals=true&vals=test", false),
682            json!({"vals": [true, "2.5", true, "test"]})
683        );
684    }
685
686    #[test]
687    fn test_utf8_chinese_characters() {
688        let result = parse_query_string("name=中文".as_bytes(), '&');
689        assert_eq!(result, vec![(String::from("name"), String::from("中文"))]);
690    }
691
692    #[test]
693    fn test_utf8_emoji() {
694        let result = parse_query_string("emoji=🚀".as_bytes(), '&');
695        assert_eq!(result, vec![(String::from("emoji"), String::from("🚀"))]);
696    }
697
698    #[test]
699    fn test_utf8_mixed_with_encoding() {
700        let result = parse_query_string("text=hello%20中文".as_bytes(), '&');
701        assert_eq!(result, vec![(String::from("text"), String::from("hello 中文"))]);
702    }
703
704    #[test]
705    fn test_custom_separator_semicolon() {
706        let result = parse_query_string(b"a=1;b=2;c=3", ';');
707        assert_eq!(
708            result,
709            vec![
710                (String::from("a"), String::from("1")),
711                (String::from("b"), String::from("2")),
712                (String::from("c"), String::from("3")),
713            ]
714        );
715    }
716
717    #[test]
718    fn test_custom_separator_comma() {
719        let result = parse_query_string(b"a=1,b=2,c=3", ',');
720        assert_eq!(
721            result,
722            vec![
723                (String::from("a"), String::from("1")),
724                (String::from("b"), String::from("2")),
725                (String::from("c"), String::from("3")),
726            ]
727        );
728    }
729
730    #[test]
731    fn test_percent_encoding_all_byte_values() {
732        let result = parse_query_string(b"space=%20&at=%40&hash=%23&dollar=%24", '&');
733        assert_eq!(
734            result,
735            vec![
736                (String::from("space"), String::from(" ")),
737                (String::from("at"), String::from("@")),
738                (String::from("hash"), String::from("#")),
739                (String::from("dollar"), String::from("$")),
740            ]
741        );
742    }
743
744    #[test]
745    fn test_high_byte_values_in_percent_encoding() {
746        let result = parse_query_string(b"high=%ff%fe%fd", '&');
747        assert_eq!(result.len(), 1);
748        assert_eq!(result[0].0, "high");
749    }
750
751    #[test]
752    fn test_very_long_query_string() {
753        let mut long_query = String::from("key=");
754        long_query.push_str(&"a".repeat(10000));
755        let result = parse_query_string(long_query.as_bytes(), '&');
756        assert_eq!(result.len(), 1);
757        assert_eq!(result[0].0, "key");
758        assert_eq!(result[0].1.len(), 10000);
759    }
760
761    #[test]
762    fn test_very_large_number_of_parameters() {
763        let mut query = String::new();
764        for i in 0..100 {
765            if i > 0 {
766                query.push('&');
767            }
768            query.push_str(&format!("param{}=value{}", i, i));
769        }
770        let result = parse_query_string(query.as_bytes(), '&');
771        assert_eq!(result.len(), 100);
772        assert_eq!(result[0].0, "param0");
773        assert_eq!(result[99].0, "param99");
774    }
775
776    #[test]
777    fn test_literal_space_in_value() {
778        let result = parse_query_string(b"name=hello world", '&');
779        assert_eq!(result, vec![(String::from("name"), String::from("hello world"))]);
780    }
781
782    #[test]
783    fn test_tab_in_value() {
784        let result = parse_query_string(b"name=hello\tworld", '&');
785        assert_eq!(result, vec![(String::from("name"), String::from("hello\tworld"))]);
786    }
787
788    #[test]
789    fn test_newline_in_value() {
790        let result = parse_query_string(b"name=hello\nworld", '&');
791        assert_eq!(result, vec![(String::from("name"), String::from("hello\nworld"))]);
792    }
793}