Skip to main content

rainier_http/
input.rs

1//! Turning query strings and form bodies into a uniform JSON tree.
2//!
3//! Everything downstream of the request — `input()`, validation rules,
4//! `Form<T>` deserialisation — reads one shape: a [`serde_json::Value`]. A JSON
5//! body already is one; a query string or a urlencoded form has to be lifted
6//! into one, and that lift is where PHP-style bracket notation is honoured:
7//!
8//! | Query | Value |
9//! |---|---|
10//! | `a=1&b=2` | `{"a": "1", "b": "2"}` |
11//! | `tags[]=x&tags[]=y` | `{"tags": ["x", "y"]}` |
12//! | `user[name]=ada` | `{"user": {"name": "ada"}}` |
13//! | `a=1&a=2` | `{"a": ["1", "2"]}` |
14//!
15//! Values stay **strings**. A form has no types — `?active=1` could be the
16//! number one or the string "1" — so guessing here would make
17//! `Form<T>`/validation behaviour depend on what the value happened to look
18//! like. Coercion is the validator's job, where the expected type is known.
19
20use serde_json::{Map, Value};
21
22/// Parse `application/x-www-form-urlencoded` bytes (a query string or a form
23/// body) into a JSON object.
24pub 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
32/// Insert `value` into `target` at a possibly-bracketed `key`.
33pub(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
40/// Walk `segments`, creating containers of whatever kind the *next* segment
41/// needs, and place `value` at the end.
42///
43/// Recursion rather than a cursor loop, because the container kind at each
44/// level is only known from the segment after it — `a[b]` needs `a` to be an
45/// object, `a[]` needs it to be an array — and a loop would have to look ahead
46/// and re-descend to fix up what it had already created.
47fn insert_segments(target: &mut Value, segments: &[Segment], value: Value) {
48    match segments {
49        [] => *target = value,
50
51        // Final named key: set it, or fold a repeat into an array.
52        [Segment::Key(name)] => {
53            let map = as_object(target);
54            match map.get_mut(name) {
55                // A repeated plain key (`a=1&a=2`) becomes an array, as every
56                // server-side form parser has done for decades.
57                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        // Final `[]`: append.
69        [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        // `a[][b]=1` — each `[]` starts a fresh element.
77        [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
86/// Coerce `target` into an object, replacing whatever was there.
87///
88/// A conflict (`a=1&a[b]=2`) can only be resolved by picking one shape; the
89/// later, more specific key wins, which is the same call PHP makes.
90fn 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
97/// Coerce `target` into an array, replacing whatever was there.
98fn 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/// One step of a bracketed key.
106#[derive(Debug, Clone, PartialEq)]
107enum Segment {
108    /// A named key.
109    Key(String),
110    /// An empty `[]`, meaning "append".
111    Append,
112}
113
114/// `user[address][city]` → `[Key("user"), Key("address"), Key("city")]`;
115/// `tags[]` → `[Key("tags"), Append]`.
116fn 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            // Unbalanced — treat the remainder as a literal key rather than
127            // silently discarding it.
128            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
143/// Read a dotted path out of a JSON tree. `items.0.name` indexes arrays.
144pub 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
156/// Render a JSON value as the string an `input()` caller expects.
157///
158/// Scalars stringify; containers do not (a caller asking for a string does not
159/// want `[object Object]`), so they read as `None`.
160pub 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
169/// Merge `overlay` over `base`, recursing into objects.
170pub 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        // Not `1` — a form carries no types, so coercion belongs to whoever
203        // knows the target type.
204        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        // Regression guard: creating the parent container must not discard the
259        // array built by the previous value for the same key.
260        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}