1#![warn(missing_docs)]
2use serde_json::Error;
71use serde_json::Value;
72
73fn to_value<T: serde::ser::Serialize>(value: &T) -> Result<serde_json::Value, Error> {
74 serde_json::to_value(value)
75}
76
77fn from_value<T: serde::ser::Serialize + serde::de::DeserializeOwned>(
78 value: serde_json::Value,
79) -> Result<T, Error> {
80 serde_json::from_value(value)
81}
82
83fn merge_value(a: &mut Value, b: &Value) {
84 match (a, b) {
85 (Value::Object(ref mut a), &Value::Object(ref b)) => {
86 for (k, v) in b {
87 merge_value(a.entry(k).or_insert(Value::Null), v);
88 }
89 }
90 (Value::Array(ref mut a), &Value::Array(ref b)) => {
91 a.extend(b.clone());
92 }
93 (Value::Array(ref mut a), &Value::Object(ref b)) => {
94 a.extend([Value::Object(b.clone())]);
95 }
96 (_, Value::Null) => {} (a, b) => {
98 *a = b.clone();
99 }
100 }
101}
102
103pub fn merge<T: serde::ser::Serialize + serde::de::DeserializeOwned>(
111 base: &T,
112 overrides: &T,
113) -> Result<T, Error> {
114 let mut left = to_value(base)?;
115 let right = to_value(overrides)?;
116 merge_value(&mut left, &right);
117 from_value(left)
118}
119
120#[cfg(test)]
121mod tests {
122 use std::collections::BTreeMap;
123
124 use serde::{Deserialize, Serialize};
125
126 use super::*;
127 use insta::assert_yaml_snapshot;
128
129 #[derive(Serialize, Deserialize)]
130 struct Data {
131 is_root: Option<bool>,
132 folders: Vec<Folder>,
133 entries: Option<BTreeMap<String, Entry>>, }
135
136 #[derive(Serialize, Deserialize)]
137 struct Folder {
138 name: String,
139 num_files: Option<u32>,
140 }
141
142 #[derive(Serialize, Deserialize)]
143 struct Entry {
144 name: String,
145 size: u32,
146 }
147 #[test]
148 fn test_merge_left_empty() {
149 let left: Data = serde_json::from_str(
150 r###"
151 {
152 "is_root": false,
153 "folders": []
154 }
155 "###,
156 )
157 .unwrap();
158 let right: Data = serde_json::from_str(
159 r###"
160 {
161 "is_root": true,
162 "folders":[
163 {
164 "name": "/var/log",
165 "num_files": 20
166 }
167 ],
168 "entries": {
169 "/var/log/f1": {
170 "name":"f1",
171 "size": 12
172 }
173 }
174 }
175 "###,
176 )
177 .unwrap();
178 assert_yaml_snapshot!(merge(&left, &right).unwrap());
179 }
180 #[test]
181 fn test_merge_right_empty() {
182 let right: Data = serde_json::from_str(
183 r###"
184 {
185 "is_root": false,
186 "folders": []
187 }
188 "###,
189 )
190 .unwrap();
191 let left: Data = serde_json::from_str(
192 r###"
193 {
194 "is_root": true,
195 "folders":[
196 {
197 "name": "/var/log",
198 "num_files": 20
199 }
200 ],
201 "entries": {
202 "/var/log/f1": {
203 "name":"f1",
204 "size": 12
205 }
206 }
207 }
208 "###,
209 )
210 .unwrap();
211 assert_yaml_snapshot!(merge(&left, &right).unwrap());
212 }
213
214 #[test]
215 fn test_merge() {
216 let left: Data = serde_json::from_str(
217 r###"
218 {
219 "is_root": false,
220 "entries": {
221 "/var/log/f2": {
222 "name":"f2",
223 "size": 5
224 }
225 },
226 "folders": [
227 {
228 "name": "/var/log",
229 "num_files": 20
230 }
231 ]
232 }
233 "###,
234 )
235 .unwrap();
236 let right: Data = serde_json::from_str(
237 r###"
238 {
239 "folders":[],
240 "entries": {
241 "/var/log/f1": {
242 "name":"f1",
243 "size": 12
244 }
245 }
246 }
247 "###,
248 )
249 .unwrap();
250 assert_yaml_snapshot!(merge(&left, &right).unwrap());
251 }
252}