1use 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}