Skip to main content

faucet_cli/
merge.rs

1//! Deep-merge of `serde_json::Value` for matrix-row overrides.
2//!
3//! Rules:
4//! - Two objects merge recursively, keys from `overlay` win on collision.
5//! - Anything else (scalar, array, null) — `overlay` replaces `base` wholesale.
6//! - Arrays do not element-merge or concatenate. This is deliberate: matrix
7//!   rows that need to add to an inherited array should redeclare it in full.
8
9use serde_json::Value;
10
11/// In-place deep-merge `overlay` into `base`. See module docs for rules.
12pub fn merge_value(base: &mut Value, overlay: Value) {
13    match (base, overlay) {
14        (Value::Object(b), Value::Object(o)) => {
15            for (k, v) in o {
16                match b.get_mut(&k) {
17                    Some(existing) => merge_value(existing, v),
18                    None => {
19                        b.insert(k, v);
20                    }
21                }
22            }
23        }
24        (slot, overlay) => *slot = overlay,
25    }
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31    use serde_json::json;
32
33    #[test]
34    fn scalar_overlay_replaces() {
35        let mut base = json!({"a": 1, "b": 2});
36        merge_value(&mut base, json!({"b": 99}));
37        assert_eq!(base, json!({"a": 1, "b": 99}));
38    }
39
40    #[test]
41    fn objects_merge_recursively() {
42        let mut base = json!({"auth": {"type": "Bearer", "token": "x"}, "path": "/v1"});
43        merge_value(&mut base, json!({"auth": {"token": "y"}}));
44        assert_eq!(
45            base,
46            json!({"auth": {"type": "Bearer", "token": "y"}, "path": "/v1"})
47        );
48    }
49
50    #[test]
51    fn arrays_replace_wholesale() {
52        let mut base = json!({"tags": ["a", "b"]});
53        merge_value(&mut base, json!({"tags": ["c"]}));
54        assert_eq!(base, json!({"tags": ["c"]}));
55    }
56
57    #[test]
58    fn null_overlay_replaces() {
59        let mut base = json!({"x": {"y": 1}});
60        merge_value(&mut base, json!({"x": null}));
61        assert_eq!(base, json!({"x": null}));
62    }
63
64    #[test]
65    fn overlay_adds_new_keys() {
66        let mut base = json!({"a": 1});
67        merge_value(&mut base, json!({"b": 2}));
68        assert_eq!(base, json!({"a": 1, "b": 2}));
69    }
70
71    #[test]
72    fn object_into_non_object_replaces() {
73        let mut base = json!({"path": "/v1"});
74        merge_value(&mut base, json!({"path": {"foo": "bar"}}));
75        assert_eq!(base, json!({"path": {"foo": "bar"}}));
76    }
77
78    #[test]
79    fn non_object_into_object_replaces() {
80        let mut base = json!({"path": {"foo": "bar"}});
81        merge_value(&mut base, json!({"path": "/v1"}));
82        assert_eq!(base, json!({"path": "/v1"}));
83    }
84
85    #[test]
86    fn deeply_nested_merges() {
87        let mut base = json!({"a": {"b": {"c": 1, "d": 2}, "x": 10}});
88        merge_value(&mut base, json!({"a": {"b": {"c": 99, "e": 3}}}));
89        assert_eq!(
90            base,
91            json!({"a": {"b": {"c": 99, "d": 2, "e": 3}, "x": 10}})
92        );
93    }
94}