1use serde::{Deserialize, Serialize};
8use std::collections::BTreeSet;
9
10use crate::object::{Atom, Node, Object, ObjectId};
11use crate::path::StatePath;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(tag = "op")]
16pub enum DiffOp {
17 SetValue {
19 path: String,
20 old: DiffValue,
21 new: DiffValue,
22 },
23 AddKey {
25 path: String,
26 key: String,
27 value: DiffValue,
28 },
29 RemoveKey {
31 path: String,
32 key: String,
33 old_value: DiffValue,
34 },
35 AddElement {
37 path: String,
38 index: usize,
39 value: DiffValue,
40 },
41 RemoveElement {
43 path: String,
44 index: usize,
45 old_value: DiffValue,
46 },
47 AddToSet { path: String, value: DiffValue },
49 RemoveFromSet { path: String, old_value: DiffValue },
51 ChangeType {
53 path: String,
54 old_type: String,
55 new_type: String,
56 },
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[serde(untagged)]
64pub enum DiffValue {
65 Null,
66 Bool(bool),
67 Int(i64),
68 UInt(u64),
71 Float(f64),
72 String(String),
73 Bytes(Vec<u8>),
74 Complex(String),
77}
78
79impl DiffValue {
80 pub fn from_atom(atom: &Atom) -> Self {
82 match atom {
83 Atom::Null => DiffValue::Null,
84 Atom::Bool(b) => DiffValue::Bool(*b),
85 Atom::Int(i) => DiffValue::Int(*i),
86 Atom::UInt(u) => DiffValue::UInt(*u),
87 Atom::Float(f) => DiffValue::Float(*f),
88 Atom::String(s) => DiffValue::String(s.clone()),
89 Atom::Bytes(b) => DiffValue::Bytes(b.clone()),
90 }
91 }
92
93 pub fn from_node(node: &Node) -> Self {
95 match node {
96 Node::Map(entries) => DiffValue::Complex(format!("{{map: {} keys}}", entries.len())),
97 Node::List(items) => DiffValue::Complex(format!("[list: {} items]", items.len())),
98 Node::Set(items) => DiffValue::Complex(format!("{{set: {} items}}", items.len())),
99 }
100 }
101
102 pub fn from_object(obj: &Object) -> Self {
104 match obj {
105 Object::Atom(atom) => Self::from_atom(atom),
106 Object::Node(node) => Self::from_node(node),
107 }
108 }
109}
110
111pub trait ObjectResolver {
114 fn resolve(&self, id: &ObjectId) -> Option<Object>;
115}
116
117pub fn diff(
122 resolver: &dyn ObjectResolver,
123 old_root: &ObjectId,
124 new_root: &ObjectId,
125) -> Vec<DiffOp> {
126 if old_root == new_root {
128 return Vec::new();
129 }
130
131 let old_obj = resolver.resolve(old_root);
132 let new_obj = resolver.resolve(new_root);
133
134 match (old_obj, new_obj) {
135 (Some(old), Some(new)) => diff_objects(resolver, &StatePath::root(), &old, &new),
136 (None, Some(_)) => {
137 Vec::new()
140 }
141 (Some(_), None) => {
142 Vec::new()
144 }
145 (None, None) => Vec::new(),
146 }
147}
148
149fn diff_objects(
151 resolver: &dyn ObjectResolver,
152 path: &StatePath,
153 old: &Object,
154 new: &Object,
155) -> Vec<DiffOp> {
156 if old == new {
157 return Vec::new();
158 }
159
160 match (old, new) {
161 (Object::Atom(old_atom), Object::Atom(new_atom)) => {
163 vec![DiffOp::SetValue {
164 path: path.to_string(),
165 old: DiffValue::from_atom(old_atom),
166 new: DiffValue::from_atom(new_atom),
167 }]
168 }
169
170 (Object::Node(Node::Map(old_entries)), Object::Node(Node::Map(new_entries))) => {
172 diff_maps(resolver, path, old_entries, new_entries)
173 }
174
175 (Object::Node(Node::List(old_items)), Object::Node(Node::List(new_items))) => {
177 diff_lists(resolver, path, old_items, new_items)
178 }
179
180 (Object::Node(Node::Set(old_items)), Object::Node(Node::Set(new_items))) => {
182 diff_sets(resolver, path, old_items, new_items)
183 }
184
185 _ => {
187 vec![DiffOp::ChangeType {
188 path: path.to_string(),
189 old_type: type_name(old),
190 new_type: type_name(new),
191 }]
192 }
193 }
194}
195
196fn diff_maps(
197 resolver: &dyn ObjectResolver,
198 path: &StatePath,
199 old_entries: &std::collections::BTreeMap<String, ObjectId>,
200 new_entries: &std::collections::BTreeMap<String, ObjectId>,
201) -> Vec<DiffOp> {
202 let mut ops = Vec::new();
203
204 let old_keys: BTreeSet<&String> = old_entries.keys().collect();
205 let new_keys: BTreeSet<&String> = new_entries.keys().collect();
206
207 for key in old_keys.difference(&new_keys) {
209 let old_id = &old_entries[*key];
210 let old_obj = resolver.resolve(old_id);
211 ops.push(DiffOp::RemoveKey {
212 path: path.to_string(),
213 key: (*key).clone(),
214 old_value: old_obj
215 .map(|o| DiffValue::from_object(&o))
216 .unwrap_or(DiffValue::Null),
217 });
218 }
219
220 for key in new_keys.difference(&old_keys) {
222 let new_id = &new_entries[*key];
223 let new_obj = resolver.resolve(new_id);
224 ops.push(DiffOp::AddKey {
225 path: path.to_string(),
226 key: (*key).clone(),
227 value: new_obj
228 .map(|o| DiffValue::from_object(&o))
229 .unwrap_or(DiffValue::Null),
230 });
231 }
232
233 for key in old_keys.intersection(&new_keys) {
235 let old_id = &old_entries[*key];
236 let new_id = &new_entries[*key];
237
238 if old_id != new_id {
239 let child_path = path.push_key(*key);
240 let old_obj = resolver.resolve(old_id);
241 let new_obj = resolver.resolve(new_id);
242
243 match (old_obj, new_obj) {
244 (Some(old_child), Some(new_child)) => {
245 ops.extend(diff_objects(resolver, &child_path, &old_child, &new_child));
246 }
247 _ => {
248 ops.push(DiffOp::SetValue {
250 path: child_path.to_string(),
251 old: DiffValue::Null,
252 new: DiffValue::Null,
253 });
254 }
255 }
256 }
257 }
258
259 ops
260}
261
262fn diff_lists(
263 resolver: &dyn ObjectResolver,
264 path: &StatePath,
265 old_items: &[ObjectId],
266 new_items: &[ObjectId],
267) -> Vec<DiffOp> {
268 let mut ops = Vec::new();
269 let max_len = old_items.len().max(new_items.len());
270
271 for i in 0..max_len {
272 match (old_items.get(i), new_items.get(i)) {
273 (Some(old_id), Some(new_id)) => {
274 if old_id != new_id {
275 let child_path = path.push_index(i);
276 let old_obj = resolver.resolve(old_id);
277 let new_obj = resolver.resolve(new_id);
278
279 match (old_obj, new_obj) {
280 (Some(old_child), Some(new_child)) => {
281 ops.extend(diff_objects(resolver, &child_path, &old_child, &new_child));
282 }
283 _ => {
284 ops.push(DiffOp::SetValue {
285 path: child_path.to_string(),
286 old: DiffValue::Null,
287 new: DiffValue::Null,
288 });
289 }
290 }
291 }
292 }
293 (None, Some(new_id)) => {
294 let new_obj = resolver.resolve(new_id);
296 ops.push(DiffOp::AddElement {
297 path: path.to_string(),
298 index: i,
299 value: new_obj
300 .map(|o| DiffValue::from_object(&o))
301 .unwrap_or(DiffValue::Null),
302 });
303 }
304 (Some(old_id), None) => {
305 let old_obj = resolver.resolve(old_id);
307 ops.push(DiffOp::RemoveElement {
308 path: path.to_string(),
309 index: i,
310 old_value: old_obj
311 .map(|o| DiffValue::from_object(&o))
312 .unwrap_or(DiffValue::Null),
313 });
314 }
315 (None, None) => unreachable!(),
316 }
317 }
318
319 ops
320}
321
322fn diff_sets(
323 resolver: &dyn ObjectResolver,
324 path: &StatePath,
325 old_items: &[ObjectId],
326 new_items: &[ObjectId],
327) -> Vec<DiffOp> {
328 let mut ops = Vec::new();
329
330 let old_set: BTreeSet<&ObjectId> = old_items.iter().collect();
331 let new_set: BTreeSet<&ObjectId> = new_items.iter().collect();
332
333 for id in old_set.difference(&new_set) {
335 let obj = resolver.resolve(id);
336 ops.push(DiffOp::RemoveFromSet {
337 path: path.to_string(),
338 old_value: obj
339 .map(|o| DiffValue::from_object(&o))
340 .unwrap_or(DiffValue::Null),
341 });
342 }
343
344 for id in new_set.difference(&old_set) {
346 let obj = resolver.resolve(id);
347 ops.push(DiffOp::AddToSet {
348 path: path.to_string(),
349 value: obj
350 .map(|o| DiffValue::from_object(&o))
351 .unwrap_or(DiffValue::Null),
352 });
353 }
354
355 ops
356}
357
358fn type_name(obj: &Object) -> String {
359 match obj {
360 Object::Atom(Atom::Null) => "null".to_string(),
361 Object::Atom(Atom::Bool(_)) => "bool".to_string(),
362 Object::Atom(Atom::Int(_)) | Object::Atom(Atom::UInt(_)) => "int".to_string(),
363 Object::Atom(Atom::Float(_)) => "float".to_string(),
364 Object::Atom(Atom::String(_)) => "string".to_string(),
365 Object::Atom(Atom::Bytes(_)) => "bytes".to_string(),
366 Object::Node(Node::Map(_)) => "map".to_string(),
367 Object::Node(Node::List(_)) => "list".to_string(),
368 Object::Node(Node::Set(_)) => "set".to_string(),
369 }
370}
371
372#[cfg(test)]
373mod tests {
374
375 #[test]
376 fn a_large_integer_reads_back_as_itself() {
377 let v = DiffValue::from_atom(&Atom::UInt(u64::MAX));
379 let back: DiffValue = serde_json::from_value(serde_json::to_value(&v).unwrap()).unwrap();
380 assert_eq!(back, DiffValue::UInt(u64::MAX));
381 let c = crate::merge::ConflictValue::from_object(&Object::uint(u64::MAX));
382 let back: crate::merge::ConflictValue =
383 serde_json::from_value(serde_json::to_value(&c).unwrap()).unwrap();
384 assert_eq!(back, crate::merge::ConflictValue::UInt(u64::MAX));
385 }
386
387 use super::*;
388 use std::collections::{BTreeMap, HashMap};
389
390 struct TestResolver {
392 objects: HashMap<ObjectId, Object>,
393 }
394
395 impl TestResolver {
396 fn new() -> Self {
397 Self {
398 objects: HashMap::new(),
399 }
400 }
401
402 fn store(&mut self, obj: &Object) -> ObjectId {
403 let id = obj.id();
404 self.objects.insert(id, obj.clone());
405 id
406 }
407
408 fn store_json(&mut self, value: &serde_json::Value) -> ObjectId {
409 self.store_json_inner(value)
410 }
411
412 fn store_json_inner(&mut self, value: &serde_json::Value) -> ObjectId {
413 let obj = match value {
414 serde_json::Value::Null => Object::null(),
415 serde_json::Value::Bool(b) => Object::bool(*b),
416 serde_json::Value::Number(n) => {
417 if let Some(i) = n.as_i64() {
418 Object::int(i)
419 } else {
420 Object::float(n.as_f64().unwrap())
421 }
422 }
423 serde_json::Value::String(s) => Object::string(s.clone()),
424 serde_json::Value::Array(arr) => {
425 let ids: Vec<ObjectId> = arr.iter().map(|v| self.store_json_inner(v)).collect();
426 Object::list(ids)
427 }
428 serde_json::Value::Object(map) => {
429 let mut entries = BTreeMap::new();
430 for (k, v) in map {
431 let id = self.store_json_inner(v);
432 entries.insert(k.clone(), id);
433 }
434 Object::map(entries)
435 }
436 };
437 self.store(&obj)
438 }
439 }
440
441 impl ObjectResolver for TestResolver {
442 fn resolve(&self, id: &ObjectId) -> Option<Object> {
443 self.objects.get(id).cloned()
444 }
445 }
446
447 #[test]
448 fn test_identical_trees_no_diff() {
449 let mut r = TestResolver::new();
450 let root = r.store_json(&serde_json::json!({"a": 1, "b": 2}));
451 let ops = diff(&r, &root, &root);
452 assert!(ops.is_empty());
453 }
454
455 #[test]
456 fn test_simple_value_change() {
457 let mut r = TestResolver::new();
458 let old = r.store_json(&serde_json::json!({"status": "healthy"}));
459 let new = r.store_json(&serde_json::json!({"status": "unhealthy"}));
460
461 let ops = diff(&r, &old, &new);
462 assert_eq!(ops.len(), 1);
463 match &ops[0] {
464 DiffOp::SetValue { path, old, new } => {
465 assert_eq!(path, "/status");
466 assert_eq!(*old, DiffValue::String("healthy".to_string()));
467 assert_eq!(*new, DiffValue::String("unhealthy".to_string()));
468 }
469 _ => panic!("expected SetValue, got {:?}", ops[0]),
470 }
471 }
472
473 #[test]
474 fn test_add_key() {
475 let mut r = TestResolver::new();
476 let old = r.store_json(&serde_json::json!({"a": 1}));
477 let new = r.store_json(&serde_json::json!({"a": 1, "b": 2}));
478
479 let ops = diff(&r, &old, &new);
480 assert_eq!(ops.len(), 1);
481 match &ops[0] {
482 DiffOp::AddKey { path, key, value } => {
483 assert_eq!(path, "/");
484 assert_eq!(key, "b");
485 assert_eq!(*value, DiffValue::Int(2));
486 }
487 _ => panic!("expected AddKey, got {:?}", ops[0]),
488 }
489 }
490
491 #[test]
492 fn test_remove_key() {
493 let mut r = TestResolver::new();
494 let old = r.store_json(&serde_json::json!({"a": 1, "b": 2}));
495 let new = r.store_json(&serde_json::json!({"a": 1}));
496
497 let ops = diff(&r, &old, &new);
498 assert_eq!(ops.len(), 1);
499 match &ops[0] {
500 DiffOp::RemoveKey {
501 path,
502 key,
503 old_value,
504 } => {
505 assert_eq!(path, "/");
506 assert_eq!(key, "b");
507 assert_eq!(*old_value, DiffValue::Int(2));
508 }
509 _ => panic!("expected RemoveKey, got {:?}", ops[0]),
510 }
511 }
512
513 #[test]
514 fn test_nested_changes() {
515 let mut r = TestResolver::new();
516 let old = r.store_json(&serde_json::json!({
517 "config": {
518 "network": { "subnet": "10.0.0.0/24" },
519 "dns": "8.8.8.8"
520 }
521 }));
522 let new = r.store_json(&serde_json::json!({
523 "config": {
524 "network": { "subnet": "192.168.0.0/16" },
525 "dns": "8.8.8.8"
526 }
527 }));
528
529 let ops = diff(&r, &old, &new);
530 assert_eq!(ops.len(), 1);
531 match &ops[0] {
532 DiffOp::SetValue { path, .. } => {
533 assert_eq!(path, "/config/network/subnet");
534 }
535 _ => panic!("expected SetValue at nested path"),
536 }
537 }
538
539 #[test]
540 fn test_multiple_changes() {
541 let mut r = TestResolver::new();
542 let old = r.store_json(&serde_json::json!({
543 "a": 1,
544 "b": 2,
545 "c": 3
546 }));
547 let new = r.store_json(&serde_json::json!({
548 "a": 10,
549 "b": 2,
550 "d": 4
551 }));
552
553 let ops = diff(&r, &old, &new);
554 assert_eq!(ops.len(), 3);
556
557 let op_types: Vec<String> = ops
558 .iter()
559 .map(|op| match op {
560 DiffOp::SetValue { path, .. } => format!("set:{}", path),
561 DiffOp::AddKey { key, .. } => format!("add:{}", key),
562 DiffOp::RemoveKey { key, .. } => format!("remove:{}", key),
563 _ => "other".to_string(),
564 })
565 .collect();
566
567 assert!(op_types.contains(&"set:/a".to_string()));
568 assert!(op_types.contains(&"remove:c".to_string()));
569 assert!(op_types.contains(&"add:d".to_string()));
570 }
571
572 #[test]
573 fn test_list_element_changed() {
574 let mut r = TestResolver::new();
575 let old = r.store_json(&serde_json::json!({"items": [1, 2, 3]}));
576 let new = r.store_json(&serde_json::json!({"items": [1, 99, 3]}));
577
578 let ops = diff(&r, &old, &new);
579 assert_eq!(ops.len(), 1);
580 match &ops[0] {
581 DiffOp::SetValue { path, old, new } => {
582 assert_eq!(path, "/items/1");
583 assert_eq!(*old, DiffValue::Int(2));
584 assert_eq!(*new, DiffValue::Int(99));
585 }
586 _ => panic!("expected SetValue"),
587 }
588 }
589
590 #[test]
591 fn test_list_element_added() {
592 let mut r = TestResolver::new();
593 let old = r.store_json(&serde_json::json!({"items": [1, 2]}));
594 let new = r.store_json(&serde_json::json!({"items": [1, 2, 3]}));
595
596 let ops = diff(&r, &old, &new);
597 assert_eq!(ops.len(), 1);
598 match &ops[0] {
599 DiffOp::AddElement { path, index, value } => {
600 assert_eq!(path, "/items");
601 assert_eq!(*index, 2);
602 assert_eq!(*value, DiffValue::Int(3));
603 }
604 _ => panic!("expected AddElement"),
605 }
606 }
607
608 #[test]
609 fn test_list_element_removed() {
610 let mut r = TestResolver::new();
611 let old = r.store_json(&serde_json::json!({"items": [1, 2, 3]}));
612 let new = r.store_json(&serde_json::json!({"items": [1, 2]}));
613
614 let ops = diff(&r, &old, &new);
615 assert_eq!(ops.len(), 1);
616 match &ops[0] {
617 DiffOp::RemoveElement {
618 path,
619 index,
620 old_value,
621 } => {
622 assert_eq!(path, "/items");
623 assert_eq!(*index, 2);
624 assert_eq!(*old_value, DiffValue::Int(3));
625 }
626 _ => panic!("expected RemoveElement"),
627 }
628 }
629
630 #[test]
631 fn test_type_change() {
632 let mut r = TestResolver::new();
633 let old = r.store_json(&serde_json::json!({"value": "string"}));
634 let new = r.store_json(&serde_json::json!({"value": 42}));
635
636 let ops = diff(&r, &old, &new);
637 assert_eq!(ops.len(), 1);
638 match &ops[0] {
639 DiffOp::SetValue { path, .. } => {
640 assert_eq!(path, "/value");
641 }
642 _ => panic!("expected SetValue for atom type change"),
643 }
644 }
645
646 #[test]
647 fn test_structural_type_change() {
648 let mut r = TestResolver::new();
649 let _old = r.store_json(&serde_json::json!({"value": "was a string"}));
650 let new_list = Object::list(vec![r.store(&Object::int(1))]);
651 let new_id = r.store(&new_list);
652
653 let mut entries = BTreeMap::new();
654 entries.insert("value".to_string(), new_id);
655 let new_root = r.store(&Object::map(entries));
656
657 let old = r.store_json(&serde_json::json!({"value": "was a string"}));
658 let ops = diff(&r, &old, &new_root);
659
660 assert_eq!(ops.len(), 1);
661 match &ops[0] {
662 DiffOp::ChangeType {
663 path,
664 old_type,
665 new_type,
666 } => {
667 assert_eq!(path, "/value");
668 assert_eq!(old_type, "string");
669 assert_eq!(new_type, "list");
670 }
671 _ => panic!("expected ChangeType"),
672 }
673 }
674
675 #[test]
676 fn test_diff_serializable() {
677 let mut r = TestResolver::new();
678 let old = r.store_json(&serde_json::json!({"a": 1}));
679 let new = r.store_json(&serde_json::json!({"a": 2, "b": 3}));
680
681 let ops = diff(&r, &old, &new);
682 let json = serde_json::to_string_pretty(&ops).unwrap();
683 assert!(json.contains("SetValue"));
684 assert!(json.contains("AddKey"));
685
686 let parsed: Vec<DiffOp> = serde_json::from_str(&json).unwrap();
688 assert_eq!(parsed, ops);
689 }
690}