Skip to main content

suture_driver_toml/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2use suture_driver::{DriverError, SemanticChange, SutureDriver};
3use suture_driver::impl_structured_driver;
4use toml::Value;
5
6pub struct TomlDriver;
7
8impl TomlDriver {
9    fn value_to_string(val: &Value) -> String {
10        match val {
11            Value::String(s) => s.clone(),
12            other => other.to_string(),
13        }
14    }
15
16    fn child_path(parent: &str, key: &str) -> String {
17        if parent == "/" {
18            format!("/{key}")
19        } else {
20            format!("{parent}/{key}")
21        }
22    }
23}
24
25impl_structured_driver! {
26    driver = TomlDriver,
27    name = "TOML",
28    extensions = [".toml"],
29    value_ty = Value,
30
31    obj_pat = |_m| Value::Table(_m),
32    arr_pat = |_v| Value::Array(_v),
33
34    new_map = toml::Table::new(),
35    wrap_map = |m| Value::Table(m),
36    wrap_arr = |v| Value::Array(v),
37
38    key_set = |map| map.keys().map(|s| s.as_str()).collect::<std::collections::HashSet<&str>>(),
39    map_get = |map, key| map.get(*key),
40    map_insert = |map, key, val| { map.insert(key.to_string(), val); },
41
42    val_str = |v| TomlDriver::value_to_string(v),
43    child_path = |parent, key| TomlDriver::child_path(parent, key),
44
45    parse_val = |s| s.parse::<Value>().map_err(|e: toml::de::Error| DriverError::ParseError(e.to_string())),
46    serialize_val = |v| toml::to_string_pretty(v).map_err(|e| DriverError::SerializationError(e.to_string())),
47
48    arrow = "->",
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_toml_driver_name() {
57        let driver = TomlDriver::new();
58        assert_eq!(driver.name(), "TOML");
59    }
60
61    #[test]
62    fn test_toml_driver_extensions() {
63        let driver = TomlDriver::new();
64        assert_eq!(driver.supported_extensions(), &[".toml"]);
65    }
66
67    #[test]
68    fn test_toml_diff_modified() {
69        let driver = TomlDriver::new();
70        let old = "name = \"Alice\"\nage = 30\n";
71        let new = "name = \"Bob\"\nage = 30\n";
72
73        let changes = driver.diff(Some(old), new).unwrap();
74        assert!(changes.contains(&SemanticChange::Modified {
75            path: "/name".to_string(),
76            old_value: "Alice".to_string(),
77            new_value: "Bob".to_string(),
78        }));
79    }
80
81    #[test]
82    fn test_toml_diff_added() {
83        let driver = TomlDriver::new();
84        let old = "name = \"Alice\"\n";
85        let new = "name = \"Alice\"\nemail = \"alice@example.com\"\n";
86
87        let changes = driver.diff(Some(old), new).unwrap();
88        assert!(changes.contains(&SemanticChange::Added {
89            path: "/email".to_string(),
90            value: "alice@example.com".to_string(),
91        }));
92    }
93
94    #[test]
95    fn test_toml_diff_nested() {
96        let driver = TomlDriver::new();
97        let old = "[server]\nhost = \"localhost\"\nport = 8080\n";
98        let new = "[server]\nhost = \"0.0.0.0\"\nport = 8080\n";
99
100        let changes = driver.diff(Some(old), new).unwrap();
101        assert!(changes.contains(&SemanticChange::Modified {
102            path: "/server/host".to_string(),
103            old_value: "localhost".to_string(),
104            new_value: "0.0.0.0".to_string(),
105        }));
106    }
107
108    #[test]
109    fn test_toml_merge_no_conflict() {
110        let driver = TomlDriver::new();
111        let base = "a = 1\nb = 2\nc = 3\n";
112        let ours = "a = 10\nb = 2\nc = 3\n";
113        let theirs = "a = 1\nb = 2\nc = 30\n";
114
115        let result = driver.merge(base, ours, theirs).unwrap();
116        assert!(result.is_some());
117        let merged: Value = result.unwrap().parse().unwrap();
118        assert_eq!(merged["a"], Value::Integer(10));
119        assert_eq!(merged["b"], Value::Integer(2));
120        assert_eq!(merged["c"], Value::Integer(30));
121    }
122
123    #[test]
124    fn test_toml_merge_conflict() {
125        let driver = TomlDriver::new();
126        let base = "key = \"original\"\n";
127        let ours = "key = \"ours\"\n";
128        let theirs = "key = \"theirs\"\n";
129
130        let result = driver.merge(base, ours, theirs).unwrap();
131        assert!(result.is_none());
132    }
133
134    #[test]
135    fn test_correctness_merge_determinism() {
136        let driver = TomlDriver::new();
137        let base = "a = 1\nb = 2\nc = 3\n";
138        let ours = "a = 10\nb = 2\nd = 4\n";
139        let theirs = "a = 1\nb = 20\ne = 5\n";
140
141        let r1 = driver.merge(base, ours, theirs).unwrap();
142        let r2 = driver.merge(base, theirs, ours).unwrap();
143        assert_eq!(r1.is_some(), r2.is_some());
144        if let (Some(m1), Some(m2)) = (r1, r2) {
145            let v1: Value = m1.parse().unwrap();
146            let v2: Value = m2.parse().unwrap();
147            assert_eq!(v1, v2, "merge must be commutative");
148        }
149    }
150
151    #[test]
152    fn test_correctness_merge_idempotency() {
153        let driver = TomlDriver::new();
154        let base = "a = 1\nb = 2\n";
155        let ours = "a = 10\nb = 2\nc = 3\n";
156
157        let result = driver.merge(base, ours, ours).unwrap();
158        assert!(result.is_some());
159        let merged: Value = result.unwrap().parse().unwrap();
160        let expected: Value = ours.parse().unwrap();
161        assert_eq!(
162            merged, expected,
163            "merge(base, ours, ours) should equal ours"
164        );
165    }
166
167    #[test]
168    fn test_correctness_base_equals_ours() {
169        let driver = TomlDriver::new();
170        let base = "a = 1\nb = 2\n";
171        let theirs = "a = 10\nb = 2\nc = 3\n";
172
173        let result = driver.merge(base, base, theirs).unwrap();
174        assert!(result.is_some());
175        let merged: Value = result.unwrap().parse().unwrap();
176        let expected: Value = theirs.parse().unwrap();
177        assert_eq!(merged, expected);
178    }
179
180    #[test]
181    fn test_correctness_base_equals_theirs() {
182        let driver = TomlDriver::new();
183        let base = "a = 1\nb = 2\n";
184        let ours = "a = 10\nb = 2\nc = 3\n";
185
186        let result = driver.merge(base, ours, base).unwrap();
187        assert!(result.is_some());
188        let merged: Value = result.unwrap().parse().unwrap();
189        let expected: Value = ours.parse().unwrap();
190        assert_eq!(merged, expected);
191    }
192
193    #[test]
194    fn test_correctness_all_equal() {
195        let driver = TomlDriver::new();
196        let content = "x = 42\ny = \"hello\"\n";
197
198        let result = driver.merge(content, content, content).unwrap();
199        assert!(result.is_some());
200        let merged: Value = result.unwrap().parse().unwrap();
201        let expected: Value = content.parse().unwrap();
202        assert_eq!(merged, expected);
203    }
204
205    #[test]
206    fn test_correctness_both_add_different_keys() {
207        let driver = TomlDriver::new();
208        let base = "shared = true\n";
209        let ours = "shared = true\nfrom_ours = 100\n";
210        let theirs = "shared = true\nfrom_theirs = 200\n";
211
212        let result = driver.merge(base, ours, theirs).unwrap();
213        assert!(result.is_some());
214        let merged: Value = result.unwrap().parse().unwrap();
215        assert_eq!(merged["shared"], Value::Boolean(true));
216        assert_eq!(merged["from_ours"], Value::Integer(100));
217        assert_eq!(merged["from_theirs"], Value::Integer(200));
218    }
219
220    #[test]
221    fn test_correctness_both_modify_different_keys() {
222        let driver = TomlDriver::new();
223        let base = "a = 1\nb = 2\nc = 3\n";
224        let ours = "a = 10\nb = 2\nc = 3\n";
225        let theirs = "a = 1\nb = 2\nc = 30\n";
226
227        let result = driver.merge(base, ours, theirs).unwrap();
228        assert!(result.is_some());
229        let merged: Value = result.unwrap().parse().unwrap();
230        assert_eq!(merged["a"], Value::Integer(10));
231        assert_eq!(merged["c"], Value::Integer(30));
232        assert_eq!(merged["b"], Value::Integer(2));
233    }
234
235    #[test]
236    fn test_correctness_both_modify_same_key_same_value() {
237        let driver = TomlDriver::new();
238        let base = "key = \"original\"\n";
239        let ours = "key = \"changed\"\n";
240        let theirs = "key = \"changed\"\n";
241
242        let result = driver.merge(base, ours, theirs).unwrap();
243        assert!(result.is_some(), "identical changes should not conflict");
244        let merged: Value = result.unwrap().parse().unwrap();
245        assert_eq!(merged["key"], Value::String("changed".to_string()));
246    }
247
248    #[test]
249    fn test_correctness_both_modify_same_key_different_value() {
250        let driver = TomlDriver::new();
251        let base = "key = \"original\"\n";
252        let ours = "key = \"ours\"\n";
253        let theirs = "key = \"theirs\"\n";
254
255        let result = driver.merge(base, ours, theirs).unwrap();
256        assert!(result.is_none());
257    }
258
259    #[test]
260    fn test_correctness_deeply_nested_merge() {
261        let driver = TomlDriver::new();
262        let base = "[l1.l2.l3]\na = 1\nb = 2\nc = 3\n";
263        let ours = "[l1.l2.l3]\na = 10\nb = 2\nc = 3\n";
264        let theirs = "[l1.l2.l3]\na = 1\nb = 2\nc = 30\n";
265
266        let result = driver.merge(base, ours, theirs).unwrap();
267        assert!(result.is_some());
268        let merged: Value = result.unwrap().parse().unwrap();
269        assert_eq!(merged["l1"]["l2"]["l3"]["a"], Value::Integer(10));
270        assert_eq!(merged["l1"]["l2"]["l3"]["c"], Value::Integer(30));
271        assert_eq!(merged["l1"]["l2"]["l3"]["b"], Value::Integer(2));
272    }
273
274    #[test]
275    fn test_correctness_unicode_keys_and_values() {
276        let driver = TomlDriver::new();
277        let base = "name = \"Taro\"\nage = 30\n";
278        let ours = "name = \"Taro\"\nage = 31\n";
279        let theirs = "name = \"Jiro\"\nage = 30\n";
280
281        let result = driver.merge(base, ours, theirs).unwrap();
282        assert!(result.is_some());
283        let merged: Value = result.unwrap().parse().unwrap();
284        assert_eq!(merged["name"], Value::String("Jiro".to_string()));
285        assert_eq!(merged["age"], Value::Integer(31));
286    }
287
288    #[test]
289    fn test_correctness_unicode_values_in_strings() {
290        let driver = TomlDriver::new();
291        let base = "greeting = \"Hello\"\nfarewell = \"Goodbye\"\n";
292        let ours = "greeting = \"こんにちは\"\nfarewell = \"Goodbye\"\n";
293        let theirs = "greeting = \"Hello\"\nfarewell = \"さようなら\"\n";
294
295        let result = driver.merge(base, ours, theirs).unwrap();
296        assert!(result.is_some());
297        let merged: Value = result.unwrap().parse().unwrap();
298        assert_eq!(merged["greeting"], Value::String("こんにちは".to_string()));
299        assert_eq!(merged["farewell"], Value::String("さようなら".to_string()));
300    }
301
302    #[test]
303    fn test_correctness_large_file() {
304        let driver = TomlDriver::new();
305        let mut base_lines = Vec::new();
306        let mut ours_lines = Vec::new();
307        let mut theirs_lines = Vec::new();
308
309        for i in 0..500 {
310            let line = format!("key_{i} = \"value_{i}\"");
311            base_lines.push(line.clone());
312            ours_lines.push(if i == 100 {
313                format!("key_{i} = \"modified_by_ours\"")
314            } else {
315                line.clone()
316            });
317            theirs_lines.push(if i == 400 {
318                format!("key_{i} = \"modified_by_theirs\"")
319            } else {
320                line
321            });
322        }
323
324        let base = base_lines.join("\n") + "\n";
325        let ours = ours_lines.join("\n") + "\n";
326        let theirs = theirs_lines.join("\n") + "\n";
327
328        let result = driver.merge(&base, &ours, &theirs).unwrap();
329        assert!(result.is_some());
330        let merged: Value = result.unwrap().parse().unwrap();
331        assert_eq!(
332            merged["key_100"],
333            Value::String("modified_by_ours".to_string())
334        );
335        assert_eq!(
336            merged["key_400"],
337            Value::String("modified_by_theirs".to_string())
338        );
339        assert_eq!(merged["key_0"], Value::String("value_0".to_string()));
340        assert_eq!(merged["key_499"], Value::String("value_499".to_string()));
341    }
342
343    #[test]
344    fn test_correctness_output_validity() {
345        let driver = TomlDriver::new();
346        let base = "[server]\nhost = \"localhost\"\nport = 8080\n";
347        let ours = "[server]\nhost = \"0.0.0.0\"\nport = 8080\n";
348        let theirs = "[server]\nhost = \"localhost\"\nport = 9090\n";
349
350        let result = driver.merge(base, ours, theirs).unwrap();
351        assert!(result.is_some());
352        let merged_str = result.unwrap();
353        let merged: Value = merged_str
354            .parse()
355            .unwrap_or_else(|e: toml::de::Error| panic!("merged output should be valid TOML: {e}"));
356        assert_eq!(
357            merged["server"]["host"],
358            Value::String("0.0.0.0".to_string())
359        );
360        assert_eq!(merged["server"]["port"], Value::Integer(9090));
361    }
362
363    #[test]
364    fn test_correctness_array_of_tables_merge() {
365        let driver = TomlDriver::new();
366        let base = "[[items]]\nname = \"a\"\n\n[[items]]\nname = \"b\"\n";
367        let ours = "[[items]]\nname = \"x\"\n\n[[items]]\nname = \"b\"\n";
368        let theirs =
369            "[[items]]\nname = \"a\"\n\n[[items]]\nname = \"b\"\n\n[[items]]\nname = \"c\"\n";
370
371        let result = driver.merge(base, ours, theirs).unwrap();
372        assert!(result.is_some());
373        let merged: Value = result.unwrap().parse().unwrap();
374        let arr = merged["items"].as_array().unwrap();
375        assert_eq!(arr[0]["name"], Value::String("x".to_string()));
376        assert_eq!(arr[1]["name"], Value::String("b".to_string()));
377        assert_eq!(arr[2]["name"], Value::String("c".to_string()));
378        assert_eq!(arr.len(), 3);
379    }
380
381    #[test]
382    fn test_correctness_inline_table_merge() {
383        let driver = TomlDriver::new();
384        let base = "point = { x = 1, y = 2 }\n";
385        let ours = "point = { x = 10, y = 2 }\n";
386        let theirs = "point = { x = 1, y = 20 }\n";
387
388        let result = driver.merge(base, ours, theirs).unwrap();
389        assert!(result.is_some());
390        let merged: Value = result.unwrap().parse().unwrap();
391        assert_eq!(merged["point"]["x"], Value::Integer(10));
392        assert_eq!(merged["point"]["y"], Value::Integer(20));
393    }
394
395    #[test]
396    fn test_correctness_dotted_key_merge() {
397        let driver = TomlDriver::new();
398        let base = "a.b.c = 1\na.b.d = 2\n";
399        let ours = "a.b.c = 10\na.b.d = 2\n";
400        let theirs = "a.b.c = 1\na.b.d = 20\n";
401
402        let result = driver.merge(base, ours, theirs).unwrap();
403        assert!(result.is_some());
404        let merged: Value = result.unwrap().parse().unwrap();
405        assert_eq!(merged["a"]["b"]["c"], Value::Integer(10));
406        assert_eq!(merged["a"]["b"]["d"], Value::Integer(20));
407    }
408
409    #[test]
410    fn test_correctness_boolean_merge() {
411        let driver = TomlDriver::new();
412        let base = "enabled = true\nverbose = false\n";
413        let ours = "enabled = false\nverbose = false\n";
414        let theirs = "enabled = true\nverbose = true\n";
415
416        let result = driver.merge(base, ours, theirs).unwrap();
417        assert!(result.is_some());
418        let merged: Value = result.unwrap().parse().unwrap();
419        assert_eq!(merged["enabled"], Value::Boolean(false));
420        assert_eq!(merged["verbose"], Value::Boolean(true));
421    }
422
423    #[test]
424    fn test_correctness_array_merge() {
425        let driver = TomlDriver::new();
426        let base = "ports = [8080, 8081]\n";
427        let ours = "ports = [9090, 8081]\n";
428        let theirs = "ports = [8080, 8081, 8082]\n";
429
430        let result = driver.merge(base, ours, theirs).unwrap();
431        assert!(result.is_some());
432        let merged: Value = result.unwrap().parse().unwrap();
433        let arr = merged["ports"].as_array().unwrap();
434        assert_eq!(arr[0], Value::Integer(9090));
435        assert_eq!(arr[1], Value::Integer(8081));
436        assert_eq!(arr[2], Value::Integer(8082));
437        assert_eq!(arr.len(), 3);
438    }
439
440    #[test]
441    fn test_correctness_empty_table() {
442        let driver = TomlDriver::new();
443        let base = "";
444        let ours = "a = 1\n";
445        let theirs = "b = 2\n";
446
447        let result = driver.merge(base, ours, theirs).unwrap();
448        assert!(result.is_some());
449        let merged: Value = result.unwrap().parse().unwrap();
450        assert_eq!(merged["a"], Value::Integer(1));
451        assert_eq!(merged["b"], Value::Integer(2));
452    }
453
454    #[test]
455    fn test_correctness_key_deletion_by_ours() {
456        let driver = TomlDriver::new();
457        let base = "a = 1\nb = 2\nc = 3\n";
458        let ours = "a = 1\nc = 3\n";
459        let theirs = "a = 1\nb = 2\nc = 3\n";
460
461        let result = driver.merge(base, ours, theirs).unwrap();
462        assert!(result.is_some());
463        let merged: Value = result.unwrap().parse().unwrap();
464        assert_eq!(
465            merged["b"],
466            Value::Integer(2),
467            "theirs kept 'b' since ours deleted it but theirs didn't"
468        );
469    }
470
471    #[test]
472    fn test_correctness_float_values() {
473        let driver = TomlDriver::new();
474        let base = "pi = 3.14\ne = 2.71\n";
475        let ours = "pi = 3.14159\ne = 2.71\n";
476        let theirs = "pi = 3.14\ne = 2.71828\n";
477
478        let result = driver.merge(base, ours, theirs).unwrap();
479        assert!(result.is_some());
480        let merged: Value = result.unwrap().parse().unwrap();
481        assert_eq!(merged["pi"], Value::Float(3.14159));
482        assert_eq!(merged["e"], Value::Float(2.71828));
483    }
484
485    #[test]
486    fn test_correctness_nested_table_different_sections() {
487        let driver = TomlDriver::new();
488        let base = "[server]\nhost = \"localhost\"\nport = 8080\n\n[database]\nhost = \"localhost\"\nport = 5432\n";
489        let ours = "[server]\nhost = \"0.0.0.0\"\nport = 8080\n\n[database]\nhost = \"localhost\"\nport = 5432\n";
490        let theirs = "[server]\nhost = \"localhost\"\nport = 8080\n\n[database]\nhost = \"localhost\"\nport = 5433\n";
491
492        let result = driver.merge(base, ours, theirs).unwrap();
493        assert!(result.is_some());
494        let merged: Value = result.unwrap().parse().unwrap();
495        assert_eq!(
496            merged["server"]["host"],
497            Value::String("0.0.0.0".to_string())
498        );
499        assert_eq!(merged["database"]["port"], Value::Integer(5433));
500    }
501
502    #[test]
503    fn test_correctness_merge_associativity() {
504        let driver = TomlDriver::new();
505        let base = "a = 1\nb = 2\nc = 3\nd = 4\n";
506        let a = "a = 10\nb = 2\nc = 3\nd = 4\n";
507        let b = "a = 1\nb = 20\nc = 3\nd = 4\n";
508        let c = "a = 1\nb = 2\nc = 30\nd = 4\n";
509
510        let ab = driver.merge(base, a, b).unwrap().expect("merge(base, A, B) should succeed");
511        let merge_left = driver
512            .merge(base, &ab, c)
513            .unwrap()
514            .expect("merge(base, merge(A,B), C) should succeed");
515
516        let bc = driver.merge(base, b, c).unwrap().expect("merge(base, B, C) should succeed");
517        let merge_right = driver
518            .merge(base, a, &bc)
519            .unwrap()
520            .expect("merge(base, A, merge(B,C)) should succeed");
521
522        let v_left: Value = merge_left.parse().unwrap();
523        let v_right: Value = merge_right.parse().unwrap();
524
525        assert_eq!(
526            v_left, v_right,
527            "merge(base, merge(A,B), C) must equal merge(base, A, merge(B,C))"
528        );
529        assert_eq!(v_left["a"], Value::Integer(10));
530        assert_eq!(v_left["b"], Value::Integer(20));
531        assert_eq!(v_left["c"], Value::Integer(30));
532        assert_eq!(v_left["d"], Value::Integer(4));
533    }
534}