1use crate::value::ConfigValue;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum MergeStrategy {
5 Replace,
7 ReplaceIfPresent,
9 Extend,
11 Deep,
13}
14
15pub type FieldStrategies = &'static [(&'static str, MergeStrategy)];
17
18#[must_use]
22pub fn merge(
23 base: ConfigValue,
24 override_val: ConfigValue,
25 strategies: FieldStrategies,
26) -> ConfigValue {
27 if override_val.is_null() {
28 return base;
29 }
30
31 match (base, override_val) {
32 (ConfigValue::Table(mut base_map), ConfigValue::Table(over_map)) => {
33 for (key, over_val) in over_map {
34 let strategy = strategies
35 .iter()
36 .find(|(k, _)| *k == key.as_str())
37 .map_or(MergeStrategy::Replace, |(_, s)| *s);
38
39 let current = base_map.remove(&key);
40
41 let merged = match strategy {
42 MergeStrategy::Replace => over_val,
43 MergeStrategy::ReplaceIfPresent => {
44 if over_val.is_null() {
45 if let Some(kept) = current {
46 base_map.insert(key, kept);
47 }
48 continue;
49 }
50 over_val
51 }
52 MergeStrategy::Extend => match (current, over_val) {
53 (Some(ConfigValue::Array(mut base_arr)), ConfigValue::Array(over_arr)) => {
54 base_arr.extend(over_arr);
55 ConfigValue::Array(base_arr)
56 }
57 (_, val) => val,
58 },
59 MergeStrategy::Deep => match (current, over_val) {
60 (Some(base_inner), over_inner) => merge(base_inner, over_inner, strategies),
61 (None, val) => val,
62 },
63 };
64
65 base_map.insert(key, merged);
66 }
67 ConfigValue::Table(base_map)
68 }
69 (_, override_val) => override_val,
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use std::collections::BTreeMap;
76
77 use super::*;
78
79 fn table(pairs: &[(&str, ConfigValue)]) -> ConfigValue {
80 let map: BTreeMap<String, ConfigValue> = pairs
81 .iter()
82 .map(|(k, v)| ((*k).to_owned(), v.clone()))
83 .collect();
84 ConfigValue::Table(map)
85 }
86
87 #[test]
88 fn replace_overwrites() {
89 let base = table(&[("key", ConfigValue::Integer(1))]);
90 let over = table(&[("key", ConfigValue::Integer(2))]);
91 assert_eq!(
92 merge(base, over, &[]),
93 table(&[("key", ConfigValue::Integer(2))])
94 );
95 }
96
97 #[test]
98 fn null_override_leaves_base() {
99 let base = table(&[("key", ConfigValue::Integer(1))]);
100 assert_eq!(merge(base.clone(), ConfigValue::Null, &[]), base);
101 }
102
103 #[test]
104 fn replace_if_present_keeps_base_when_override_null() {
105 let base = table(&[("key", ConfigValue::String("base".into()))]);
106 let over = table(&[("key", ConfigValue::Null)]);
107 assert_eq!(
108 merge(base, over, &[("key", MergeStrategy::ReplaceIfPresent)]),
109 table(&[("key", ConfigValue::String("base".into()))])
110 );
111 }
112
113 #[test]
114 fn replace_if_present_replaces_when_override_non_null() {
115 let base = table(&[("key", ConfigValue::Integer(1))]);
116 let over = table(&[("key", ConfigValue::Integer(2))]);
117 assert_eq!(
118 merge(base, over, &[("key", MergeStrategy::ReplaceIfPresent)]),
119 table(&[("key", ConfigValue::Integer(2))])
120 );
121 }
122
123 #[test]
124 fn deep_merge_nested_tables() {
125 let base = table(&[(
126 "nested",
127 table(&[
128 ("a", ConfigValue::Integer(1)),
129 ("b", ConfigValue::Integer(2)),
130 ]),
131 )]);
132 let over = table(&[(
133 "nested",
134 table(&[
135 ("b", ConfigValue::Integer(3)),
136 ("c", ConfigValue::Integer(4)),
137 ]),
138 )]);
139 assert_eq!(
140 merge(base, over, &[("nested", MergeStrategy::Deep)]),
141 table(&[(
142 "nested",
143 table(&[
144 ("a", ConfigValue::Integer(1)),
145 ("b", ConfigValue::Integer(3)),
146 ("c", ConfigValue::Integer(4)),
147 ])
148 )])
149 );
150 }
151
152 #[test]
153 fn extend_concatenates_arrays() {
154 let base = table(&[(
155 "tags",
156 ConfigValue::Array(vec![ConfigValue::String("a".into())]),
157 )]);
158 let over = table(&[(
159 "tags",
160 ConfigValue::Array(vec![ConfigValue::String("b".into())]),
161 )]);
162 assert_eq!(
163 merge(base, over, &[("tags", MergeStrategy::Extend)]),
164 table(&[(
165 "tags",
166 ConfigValue::Array(vec![
167 ConfigValue::String("a".into()),
168 ConfigValue::String("b".into()),
169 ])
170 )])
171 );
172 }
173}