cardinal_plugins/utils/
mod.rs1use std::collections::HashMap;
2
3pub fn parse_query_string_multi(qs: &str) -> HashMap<String, Vec<String>> {
6 let mut map: HashMap<String, Vec<String>> = HashMap::new();
7
8 for (key, value) in form_urlencoded::parse(qs.as_bytes()).into_owned() {
9 map.entry(key).or_default().push(value);
10 }
11
12 map
13}
14
15#[cfg(test)]
16mod tests {
17 use super::parse_query_string_multi;
18
19 #[test]
20 fn empty_string_returns_empty_map() {
21 let m = parse_query_string_multi("");
22 assert!(m.is_empty());
23 }
24
25 #[test]
26 fn single_pair_parses_correctly() {
27 let m = parse_query_string_multi("user=a");
28 assert_eq!(m.len(), 1);
29 assert_eq!(m.get("user"), Some(&vec!["a".to_string()]));
30 }
31
32 #[test]
33 fn repeated_keys_accumulate_in_order() {
34 let m = parse_query_string_multi("id=1&id=2&id=3");
35 assert_eq!(m.len(), 1);
36 assert_eq!(
37 m.get("id"),
38 Some(&vec!["1".to_string(), "2".to_string(), "3".to_string()])
39 );
40 }
41
42 #[test]
43 fn multiple_distinct_keys() {
44 let m = parse_query_string_multi("user=a&role=admin&user=b");
45 assert_eq!(m.len(), 2);
46 assert_eq!(m.get("role"), Some(&vec!["admin".to_string()]));
47 assert_eq!(m.get("user"), Some(&vec!["a".to_string(), "b".to_string()]));
48 }
49
50 #[test]
51 fn percent_decoding_and_plus_as_space() {
52 let m = parse_query_string_multi("name=Andr%C3%A9s+Pirela&k%2Bey=v%2Balue");
54 assert_eq!(m.get("name"), Some(&vec!["Andrés Pirela".to_string()]));
55 assert_eq!(m.get("k+ey"), Some(&vec!["v+alue".to_string()]));
57 }
58
59 #[test]
60 fn empty_value_is_kept_as_empty_string() {
61 let m = parse_query_string_multi("a=&b=");
62 assert_eq!(m.get("a"), Some(&vec!["".to_string()]));
63 assert_eq!(m.get("b"), Some(&vec!["".to_string()]));
64 }
65
66 #[test]
67 fn bare_key_gets_empty_string_value() {
68 let m = parse_query_string_multi("flag&x=1");
70 assert_eq!(m.get("flag"), Some(&vec!["".to_string()]));
71 assert_eq!(m.get("x"), Some(&vec!["1".to_string()]));
72 }
73
74 #[test]
75 fn unicode_keys_and_values() {
76 let m = parse_query_string_multi("名=値&emoji=%F0%9F%98%80"); assert_eq!(m.get("名"), Some(&vec!["値".to_string()]));
78 assert_eq!(m.get("emoji"), Some(&vec!["😀".to_string()]));
79 }
80
81 #[test]
82 fn preserves_insertion_order_within_value_vectors() {
83 let m = parse_query_string_multi("k=first&x=1&k=second&k=third&x=2");
85 assert_eq!(
86 m.get("k"),
87 Some(&vec![
88 "first".to_string(),
89 "second".to_string(),
90 "third".to_string()
91 ])
92 );
93 assert_eq!(m.get("x"), Some(&vec!["1".to_string(), "2".to_string()]));
94 }
95
96 #[test]
97 fn mixed_empty_and_nonempty_values() {
98 let m = parse_query_string_multi("k=&k=1&&k=2"); assert_eq!(
100 m.get("k"),
101 Some(&vec!["".to_string(), "1".to_string(), "2".to_string()])
102 );
103 }
104
105 #[test]
106 fn plus_in_key_and_value_becomes_space() {
107 let m = parse_query_string_multi("a+b=c+d&a+b=e+f");
108 assert_eq!(
109 m.get("a b"),
110 Some(&vec!["c d".to_string(), "e f".to_string()])
111 );
112 }
113}