1use serde_json::{Map, Value};
21
22pub fn parse_urlencoded(raw: &[u8]) -> Value {
25 let mut root = Value::Object(Map::new());
26 for (key, value) in form_urlencoded::parse(raw) {
27 insert(&mut root, &key, Value::String(value.into_owned()));
28 }
29 root
30}
31
32pub(crate) fn insert(target: &mut Value, key: &str, value: Value) {
34 let segments = parse_segments(key);
35 if !segments.is_empty() {
36 insert_segments(target, &segments, value);
37 }
38}
39
40fn insert_segments(target: &mut Value, segments: &[Segment], value: Value) {
48 match segments {
49 [] => *target = value,
50
51 [Segment::Key(name)] => {
53 let map = as_object(target);
54 match map.get_mut(name) {
55 Some(Value::Array(items)) => items.push(value),
58 Some(existing) => {
59 let previous = std::mem::replace(existing, Value::Null);
60 *existing = Value::Array(vec![previous, value]);
61 }
62 None => {
63 map.insert(name.clone(), value);
64 }
65 }
66 }
67
68 [Segment::Append] => as_array(target).push(value),
70
71 [Segment::Key(name), rest @ ..] => {
72 let child = as_object(target).entry(name.clone()).or_insert(Value::Null);
73 insert_segments(child, rest, value);
74 }
75
76 [Segment::Append, rest @ ..] => {
78 let items = as_array(target);
79 items.push(Value::Null);
80 let last = items.last_mut().expect("just pushed");
81 insert_segments(last, rest, value);
82 }
83 }
84}
85
86fn as_object(target: &mut Value) -> &mut Map<String, Value> {
91 if !target.is_object() {
92 *target = Value::Object(Map::new());
93 }
94 target.as_object_mut().expect("just ensured object")
95}
96
97fn as_array(target: &mut Value) -> &mut Vec<Value> {
99 if !target.is_array() {
100 *target = Value::Array(Vec::new());
101 }
102 target.as_array_mut().expect("just ensured array")
103}
104
105#[derive(Debug, Clone, PartialEq)]
107enum Segment {
108 Key(String),
110 Append,
112}
113
114fn parse_segments(key: &str) -> Vec<Segment> {
117 let Some(open) = key.find('[') else {
118 return vec![Segment::Key(key.to_string())];
119 };
120
121 let mut segments = vec![Segment::Key(key[..open].to_string())];
122 let mut rest = &key[open..];
123
124 while let Some(stripped) = rest.strip_prefix('[') {
125 let Some(close) = stripped.find(']') else {
126 segments.push(Segment::Key(stripped.to_string()));
129 break;
130 };
131 let name = &stripped[..close];
132 segments.push(if name.is_empty() {
133 Segment::Append
134 } else {
135 Segment::Key(name.to_string())
136 });
137 rest = &stripped[close + 1..];
138 }
139
140 segments
141}
142
143pub fn lookup<'a>(root: &'a Value, path: &str) -> Option<&'a Value> {
145 let mut cursor = root;
146 for segment in path.split('.') {
147 cursor = match cursor {
148 Value::Object(map) => map.get(segment)?,
149 Value::Array(items) => items.get(segment.parse::<usize>().ok()?)?,
150 _ => return None,
151 };
152 }
153 Some(cursor)
154}
155
156pub fn scalar_to_string(value: &Value) -> Option<String> {
161 match value {
162 Value::String(s) => Some(s.clone()),
163 Value::Number(n) => Some(n.to_string()),
164 Value::Bool(b) => Some(b.to_string()),
165 Value::Null | Value::Array(_) | Value::Object(_) => None,
166 }
167}
168
169pub fn merge(base: Value, overlay: Value) -> Value {
171 match (base, overlay) {
172 (Value::Object(mut base), Value::Object(overlay)) => {
173 for (key, value) in overlay {
174 let merged = match base.remove(&key) {
175 Some(existing) => merge(existing, value),
176 None => value,
177 };
178 base.insert(key, merged);
179 }
180 Value::Object(base)
181 }
182 (_, overlay) => overlay,
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use serde_json::json;
190
191 fn parse(query: &str) -> Value {
192 parse_urlencoded(query.as_bytes())
193 }
194
195 #[test]
196 fn parses_flat_pairs() {
197 assert_eq!(parse("a=1&b=2"), json!({ "a": "1", "b": "2" }));
198 }
199
200 #[test]
201 fn values_stay_strings() {
202 assert_eq!(parse("n=1&t=true"), json!({ "n": "1", "t": "true" }));
205 }
206
207 #[test]
208 fn percent_and_plus_decoding() {
209 assert_eq!(parse("q=hello+world"), json!({ "q": "hello world" }));
210 assert_eq!(parse("q=a%26b"), json!({ "q": "a&b" }));
211 }
212
213 #[test]
214 fn empty_input_is_an_empty_object() {
215 assert_eq!(parse(""), json!({}));
216 }
217
218 #[test]
219 fn bracket_append_builds_an_array() {
220 assert_eq!(parse("tags[]=x&tags[]=y"), json!({ "tags": ["x", "y"] }));
221 }
222
223 #[test]
224 fn a_single_bracket_append_is_still_an_array() {
225 assert_eq!(parse("tags[]=x"), json!({ "tags": ["x"] }));
226 }
227
228 #[test]
229 fn a_repeated_plain_key_collapses_to_an_array() {
230 assert_eq!(parse("a=1&a=2&a=3"), json!({ "a": ["1", "2", "3"] }));
231 }
232
233 #[test]
234 fn bracket_keys_build_nested_objects() {
235 assert_eq!(parse("user[name]=ada"), json!({ "user": { "name": "ada" } }));
236 assert_eq!(
237 parse("user[address][city]=paris"),
238 json!({ "user": { "address": { "city": "paris" } } })
239 );
240 }
241
242 #[test]
243 fn nested_objects_and_scalars_coexist() {
244 assert_eq!(
245 parse("user[name]=ada&user[age]=36&active=1"),
246 json!({ "user": { "name": "ada", "age": "36" }, "active": "1" })
247 );
248 }
249
250 #[test]
251 fn an_unbalanced_bracket_keeps_the_value() {
252 let parsed = parse("a[b=1");
253 assert!(parsed.get("a").is_some(), "{parsed}");
254 }
255
256 #[test]
257 fn repeated_appends_accumulate_into_one_array() {
258 assert_eq!(parse("t[]=a&t[]=b&t[]=c"), json!({ "t": ["a", "b", "c"] }));
261 assert_eq!(parse("u[tags][]=x&u[tags][]=y"), json!({ "u": { "tags": ["x", "y"] } }));
262 }
263
264 #[test]
265 fn an_append_of_objects_starts_a_new_element_each_time() {
266 assert_eq!(
267 parse("rows[][name]=a&rows[][name]=b"),
268 json!({ "rows": [{ "name": "a" }, { "name": "b" }] })
269 );
270 }
271
272 #[test]
273 fn a_more_specific_later_key_wins_a_shape_conflict() {
274 assert_eq!(parse("a=1&a[b]=2"), json!({ "a": { "b": "2" } }));
275 }
276
277 #[test]
278 fn dotted_lookup_walks_objects_and_arrays() {
279 let value = json!({ "user": { "roles": ["admin", "editor"] } });
280 assert_eq!(lookup(&value, "user.roles.0"), Some(&json!("admin")));
281 assert_eq!(lookup(&value, "user.roles.9"), None);
282 assert_eq!(lookup(&value, "user.missing"), None);
283 }
284
285 #[test]
286 fn only_scalars_stringify() {
287 assert_eq!(scalar_to_string(&json!("x")), Some("x".to_string()));
288 assert_eq!(scalar_to_string(&json!(3)), Some("3".to_string()));
289 assert_eq!(scalar_to_string(&json!(true)), Some("true".to_string()));
290 assert_eq!(scalar_to_string(&json!(null)), None);
291 assert_eq!(scalar_to_string(&json!({ "a": 1 })), None);
292 assert_eq!(scalar_to_string(&json!([1])), None);
293 }
294
295 #[test]
296 fn merge_is_recursive_over_objects() {
297 let merged =
298 merge(json!({ "a": { "x": 1, "y": 2 }, "b": 1 }), json!({ "a": { "y": 3 }, "c": 4 }));
299 assert_eq!(merged, json!({ "a": { "x": 1, "y": 3 }, "b": 1, "c": 4 }));
300 }
301}