1use crate::Value;
2use std::fmt;
3use std::fmt::Display;
4
5struct PatchDiffer {
6 path: String,
7 patch: super::Patch,
8 shift: usize,
9}
10
11impl PatchDiffer {
12 fn new() -> Self {
13 Self {
14 path: "/".to_string(),
15 patch: super::Patch(Vec::new()),
16 shift: 0,
17 }
18 }
19}
20
21impl<'a> treediff::Delegate<'a, PlatformItemKey, Value> for PatchDiffer {
22 fn push(&mut self, key: &PlatformItemKey) {
23 use std::fmt::Write;
24 if self.path.len() != 1 {
25 self.path.push('/');
26 }
27 match key {
28 PlatformItemKey::Index(idx) => write!(self.path, "{}", *idx).unwrap(),
29 PlatformItemKey::String(ref key) => append_path(&mut self.path, key),
30 PlatformItemKey::BigSignedIndex(idx) => write!(self.path, "{}", *idx).unwrap(),
31 PlatformItemKey::BigIndex(idx) => write!(self.path, "{}", *idx).unwrap(),
32 PlatformItemKey::SignedIndex(idx) => write!(self.path, "{}", *idx).unwrap(),
33 PlatformItemKey::Bytes(bytes) => write!(self.path, "{}", hex::encode(bytes)).unwrap(),
34 PlatformItemKey::ArrayIndex(idx) => write!(self.path, "{}", *idx - self.shift).unwrap(),
35 }
36 }
37
38 fn pop(&mut self) {
39 let mut pos = self.path.rfind('/').unwrap_or(0);
40 if pos == 0 {
41 pos = 1;
42 }
43 self.path.truncate(pos);
44 self.shift = 0;
45 }
46
47 fn removed<'b>(&mut self, k: &'b PlatformItemKey, _v: &'a Value) {
48 let len = self.path.len();
49 self.push(k);
50 self.patch
51 .0
52 .push(super::PatchOperation::Remove(super::RemoveOperation {
53 path: self.path.clone(),
54 }));
55 if let PlatformItemKey::ArrayIndex(_) = k {
57 self.shift += 1;
58 }
59 self.path.truncate(len);
60 }
61
62 fn added(&mut self, k: &PlatformItemKey, v: &Value) {
63 let len = self.path.len();
64 self.push(k);
65 self.patch
66 .0
67 .push(super::PatchOperation::Add(super::AddOperation {
68 path: self.path.clone(),
69 value: v.clone(),
70 }));
71 self.path.truncate(len);
72 }
73
74 fn modified(&mut self, _old: &'a Value, new: &'a Value) {
75 self.patch
76 .0
77 .push(super::PatchOperation::Replace(super::ReplaceOperation {
78 path: self.path.clone(),
79 value: new.clone(),
80 }));
81 }
82}
83
84fn append_path(path: &mut String, key: &str) {
85 path.reserve(key.len());
86 for ch in key.chars() {
87 if ch == '~' {
88 *path += "~0";
89 } else if ch == '/' {
90 *path += "~1";
91 } else {
92 path.push(ch);
93 }
94 }
95}
96
97pub fn diff(left: &Value, right: &Value) -> super::Patch {
144 let mut differ = PatchDiffer::new();
145 treediff::diff(left, right, &mut differ);
146 differ.patch
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
151pub enum PlatformItemKey {
152 BigSignedIndex(i128),
154 BigIndex(u128),
156 SignedIndex(i64),
158 Index(u64),
160 ArrayIndex(usize),
162 Bytes(Vec<u8>),
164 String(String),
166}
167
168impl Display for PlatformItemKey {
169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170 match *self {
171 PlatformItemKey::String(ref v) => v.fmt(f),
172 PlatformItemKey::Index(ref v) => v.fmt(f),
173 PlatformItemKey::BigSignedIndex(ref v) => v.fmt(f),
174 PlatformItemKey::BigIndex(ref v) => v.fmt(f),
175 PlatformItemKey::SignedIndex(ref v) => v.fmt(f),
176 PlatformItemKey::Bytes(ref v) => hex::encode(v).fmt(f),
177 PlatformItemKey::ArrayIndex(ref v) => v.fmt(f),
178 }
179 }
180}
181
182impl From<Value> for Option<PlatformItemKey> {
183 fn from(value: Value) -> Self {
184 match value {
185 Value::U128(i) => Some(PlatformItemKey::BigIndex(i)),
186 Value::I128(i) => Some(PlatformItemKey::BigSignedIndex(i)),
187 Value::U64(i) => Some(PlatformItemKey::Index(i)),
188 Value::I64(i) => Some(PlatformItemKey::SignedIndex(i)),
189 Value::U32(i) => Some(PlatformItemKey::Index(i as u64)),
190 Value::I32(i) => Some(PlatformItemKey::SignedIndex(i as i64)),
191 Value::U16(i) => Some(PlatformItemKey::Index(i as u64)),
192 Value::I16(i) => Some(PlatformItemKey::SignedIndex(i as i64)),
193 Value::U8(i) => Some(PlatformItemKey::Index(i as u64)),
194 Value::I8(i) => Some(PlatformItemKey::SignedIndex(i as i64)),
195 Value::Bytes(bytes) => Some(PlatformItemKey::Bytes(bytes)),
196 Value::Bytes20(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
197 Value::Bytes32(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
198 Value::Bytes36(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
199 Value::EnumU8(_) => None,
200 Value::EnumString(_) => None,
201 Value::Identifier(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
202 Value::Float(_) => None,
203 Value::Text(str) => Some(PlatformItemKey::String(str)),
204 Value::Bool(_) => None,
205 Value::Null => None,
206 Value::Array(_) => None,
207 Value::Map(_) => None,
208 }
209 }
210}
211
212impl treediff::Value for Value {
213 type Key = PlatformItemKey;
214 type Item = Value;
216 #[allow(clippy::type_complexity)]
219 fn items<'a>(&'a self) -> Option<Box<dyn Iterator<Item = (Self::Key, &'a Self::Item)> + 'a>> {
220 match *self {
221 Value::Array(ref inner) => Some(Box::new(
222 inner
223 .iter()
224 .enumerate()
225 .map(|(i, v)| (PlatformItemKey::ArrayIndex(i), v)),
226 )),
227 Value::Map(ref inner) => Some(Box::new(inner.iter().filter_map(|(s, v)| {
228 let key: Option<PlatformItemKey> = s.clone().into();
229 key.map(|k| (k, v))
230 }))),
231 _ => None,
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use crate::{from_value, platform_value, Value};
239
240 #[test]
241 pub fn replace_all() {
242 let left = platform_value!({"title": "Hello!"});
243 let p = super::diff(&left, &Value::Null);
244 assert_eq!(
245 p,
246 from_value(platform_value!([
247 { "op": "replace", "path": "/", "value": null },
248 ]))
249 .unwrap()
250 );
251 }
252
253 #[test]
254 pub fn add_all() {
255 let right = platform_value!({"title": "Hello!"});
256 let p = super::diff(&Value::Null, &right);
257 assert_eq!(
258 p,
259 from_value(platform_value!([
260 { "op": "replace", "path": "/", "value": { "title": "Hello!" } },
261 ]))
262 .unwrap()
263 );
264 }
265
266 #[test]
267 pub fn remove_all() {
268 let left = platform_value!(["hello", "bye"]);
269 let right = platform_value!([]);
270 let p = super::diff(&left, &right);
271 assert_eq!(
272 p,
273 from_value(platform_value!([
274 { "op": "remove", "path": "/0" },
275 { "op": "remove", "path": "/0" },
276 ]))
277 .unwrap()
278 );
279 }
280
281 #[test]
282 pub fn remove_tail() {
283 let left = platform_value!(["hello", "bye", "hi"]);
284 let right = platform_value!(["hello"]);
285 let p = super::diff(&left, &right);
286 assert_eq!(
287 p,
288 from_value(platform_value!([
289 { "op": "remove", "path": "/1" },
290 { "op": "remove", "path": "/1" },
291 ]))
292 .unwrap()
293 );
294 }
295 #[test]
296 pub fn replace_object() {
297 let left = platform_value!(["hello", "bye"]);
298 let right = platform_value!({"hello": "bye"});
299 let p = super::diff(&left, &right);
300 assert_eq!(
301 p,
302 from_value(platform_value!([
303 { "op": "add", "path": "/hello", "value": "bye" },
304 { "op": "remove", "path": "/0" },
305 { "op": "remove", "path": "/0" },
306 ]))
307 .unwrap()
308 );
309 }
310
311 #[test]
312 fn escape_json_keys() {
313 let mut left = platform_value!({
314 "/slashed/path": 1
315 });
316 let right = platform_value!({
317 "/slashed/path": 2,
318 });
319 let patch = super::diff(&left, &right);
320
321 eprintln!("{:?}", patch);
322
323 crate::patch(&mut left, &patch).unwrap();
324 assert_eq!(left, right);
325 }
326
327 #[test]
332 fn append_path_escapes_tilde() {
333 let mut path = String::from("/");
334 super::append_path(&mut path, "a~b");
335 assert_eq!(path, "/a~0b");
336 }
337
338 #[test]
339 fn append_path_escapes_slash() {
340 let mut path = String::from("/");
341 super::append_path(&mut path, "a/b");
342 assert_eq!(path, "/a~1b");
343 }
344
345 #[test]
346 fn append_path_escapes_both() {
347 let mut path = String::from("/");
348 super::append_path(&mut path, "~/");
349 assert_eq!(path, "/~0~1");
350 }
351
352 #[test]
353 fn append_path_no_escaping_needed() {
354 let mut path = String::from("/");
355 super::append_path(&mut path, "plain");
356 assert_eq!(path, "/plain");
357 }
358
359 #[test]
360 fn append_path_empty_key() {
361 let mut path = String::from("/");
362 super::append_path(&mut path, "");
363 assert_eq!(path, "/");
364 }
365
366 #[test]
371 fn diff_identical_scalars_returns_empty() {
372 let v = platform_value!("hello");
373 let p = super::diff(&v, &v);
374 assert!(p.0.is_empty());
375 }
376
377 #[test]
378 fn diff_identical_maps_returns_empty() {
379 let v = platform_value!({"a": 1, "b": 2});
380 let p = super::diff(&v, &v);
381 assert!(p.0.is_empty());
382 }
383
384 #[test]
385 fn diff_identical_arrays_returns_empty() {
386 let v = platform_value!([1, 2, 3]);
387 let p = super::diff(&v, &v);
388 assert!(p.0.is_empty());
389 }
390
391 #[test]
392 fn diff_identical_nested_returns_empty() {
393 let v = platform_value!({"a": {"b": [1, 2]}});
394 let p = super::diff(&v, &v);
395 assert!(p.0.is_empty());
396 }
397
398 #[test]
403 fn diff_map_added_key() {
404 let left = platform_value!({"a": 1});
405 let right = platform_value!({"a": 1, "b": 2});
406 let p = super::diff(&left, &right);
407 assert_eq!(
408 p,
409 from_value(platform_value!([
410 { "op": "add", "path": "/b", "value": 2 },
411 ]))
412 .unwrap()
413 );
414 }
415
416 #[test]
417 fn diff_map_removed_key() {
418 let left = platform_value!({"a": 1, "b": 2});
419 let right = platform_value!({"a": 1});
420 let p = super::diff(&left, &right);
421 assert_eq!(
422 p,
423 from_value(platform_value!([
424 { "op": "remove", "path": "/b" },
425 ]))
426 .unwrap()
427 );
428 }
429
430 #[test]
431 fn diff_map_modified_key() {
432 let left = platform_value!({"a": 1});
433 let right = platform_value!({"a": 2});
434 let p = super::diff(&left, &right);
435 assert_eq!(
436 p,
437 from_value(platform_value!([
438 { "op": "replace", "path": "/a", "value": 2 },
439 ]))
440 .unwrap()
441 );
442 }
443
444 #[test]
445 fn diff_map_added_removed_modified() {
446 let left = platform_value!({"a": 1, "b": 2});
447 let right = platform_value!({"a": 10, "c": 3});
448 let mut p_left = left.clone();
449 let patch = super::diff(&left, &right);
450 crate::patch(&mut p_left, &patch).unwrap();
451 assert_eq!(p_left, right);
452 }
453
454 #[test]
459 fn diff_array_append() {
460 let left = platform_value!([1, 2]);
461 let right = platform_value!([1, 2, 3]);
462 let patch = super::diff(&left, &right);
463 let mut doc = left.clone();
464 crate::patch(&mut doc, &patch).unwrap();
465 assert_eq!(doc, right);
466 }
467
468 #[test]
469 fn diff_array_removal_from_middle() {
470 let left = platform_value!([1, 2, 3]);
471 let right = platform_value!([1, 3]);
472 let patch = super::diff(&left, &right);
473 let mut doc = left.clone();
474 crate::patch(&mut doc, &patch).unwrap();
475 assert_eq!(doc, right);
476 }
477
478 #[test]
479 fn diff_array_complete_replacement() {
480 let left = platform_value!([1, 2, 3]);
481 let right = platform_value!([4, 5]);
482 let patch = super::diff(&left, &right);
483 let mut doc = left.clone();
484 crate::patch(&mut doc, &patch).unwrap();
485 assert_eq!(doc, right);
486 }
487
488 #[test]
493 fn diff_nested_map_modification() {
494 let left = platform_value!({
495 "outer": {
496 "inner": 1,
497 "keep": "same"
498 }
499 });
500 let right = platform_value!({
501 "outer": {
502 "inner": 99,
503 "keep": "same"
504 }
505 });
506 let p = super::diff(&left, &right);
507 assert_eq!(
508 p,
509 from_value(platform_value!([
510 { "op": "replace", "path": "/outer/inner", "value": 99 },
511 ]))
512 .unwrap()
513 );
514 }
515
516 #[test]
517 fn diff_nested_map_add_and_remove() {
518 let left = platform_value!({
519 "a": {
520 "b": 1,
521 "c": 2
522 }
523 });
524 let right = platform_value!({
525 "a": {
526 "c": 2,
527 "d": 3
528 }
529 });
530 let patch = super::diff(&left, &right);
531 let mut doc = left.clone();
532 crate::patch(&mut doc, &patch).unwrap();
533 assert_eq!(doc, right);
534 }
535
536 #[test]
537 fn diff_deeply_nested() {
538 let left = platform_value!({
539 "l1": {
540 "l2": {
541 "l3": "old"
542 }
543 }
544 });
545 let right = platform_value!({
546 "l1": {
547 "l2": {
548 "l3": "new"
549 }
550 }
551 });
552 let p = super::diff(&left, &right);
553 assert_eq!(
554 p,
555 from_value(platform_value!([
556 { "op": "replace", "path": "/l1/l2/l3", "value": "new" },
557 ]))
558 .unwrap()
559 );
560 }
561
562 #[test]
567 fn diff_tilde_key_round_trip() {
568 let left = platform_value!({
569 "a~b": 1
570 });
571 let right = platform_value!({
572 "a~b": 2
573 });
574 let mut doc = left.clone();
575 let patch = super::diff(&left, &right);
576 crate::patch(&mut doc, &patch).unwrap();
577 assert_eq!(doc, right);
578 }
579
580 #[test]
581 fn diff_slash_key_round_trip() {
582 let left = platform_value!({
583 "x/y": 10
584 });
585 let right = platform_value!({
586 "x/y": 20
587 });
588 let mut doc = left.clone();
589 let patch = super::diff(&left, &right);
590 crate::patch(&mut doc, &patch).unwrap();
591 assert_eq!(doc, right);
592 }
593
594 #[test]
599 fn diff_both_null_returns_empty() {
600 let p = super::diff(&Value::Null, &Value::Null);
601 assert!(p.0.is_empty());
602 }
603
604 #[test]
605 fn diff_null_to_value() {
606 let left = Value::Null;
607 let right = platform_value!(42);
608 let p = super::diff(&left, &right);
609 assert_eq!(
610 p,
611 from_value(platform_value!([
612 { "op": "replace", "path": "/", "value": 42 },
613 ]))
614 .unwrap()
615 );
616 }
617
618 #[test]
619 fn diff_value_to_null() {
620 let left = platform_value!(42);
621 let right = Value::Null;
622 let p = super::diff(&left, &right);
623 assert_eq!(
624 p,
625 from_value(platform_value!([
626 { "op": "replace", "path": "/", "value": null },
627 ]))
628 .unwrap()
629 );
630 }
631
632 #[test]
637 fn diff_scalar_to_map() {
638 let left = platform_value!(42);
639 let right = platform_value!({"a": 1});
640 let p = super::diff(&left, &right);
641 assert_eq!(
642 p,
643 from_value(platform_value!([
644 { "op": "replace", "path": "/", "value": {"a": 1} },
645 ]))
646 .unwrap()
647 );
648 }
649
650 #[test]
651 fn diff_array_with_nested_maps() {
652 let left = platform_value!([{"name": "alice"}, {"name": "bob"}]);
653 let right = platform_value!([{"name": "alice"}, {"name": "charlie"}]);
654 let patch = super::diff(&left, &right);
655 let mut doc = left.clone();
656 crate::patch(&mut doc, &patch).unwrap();
657 assert_eq!(doc, right);
658 }
659}