1#![doc = include_str!("../examples/simple_object_diff.rs")]
11#![doc = include_str!("../examples/simple_object_diff.json")]
16mod element_path_parser;
18mod rhai_script;
19
20use std::ops::{Deref, DerefMut};
21use std::str::FromStr;
22use std::time::Duration;
23use approx::relative_eq;
24use chrono::{DateTime};
25use derive_builder::Builder;
26use serde::{ser::SerializeMap, Serialize};
27use crate::element_path_parser::parse_element_path;
28
29#[derive(Debug, Serialize)]
30#[serde(tag = "entry_difference", rename_all = "snake_case")]
31pub enum EntryDifference {
32 Missing { value: serde_json::Value },
34 Extra { value: serde_json::Value },
36 Value { value_diff: Difference },
38}
39
40#[derive(Debug)]
41pub struct Map<K: Serialize, V: Serialize>(pub Vec<(K, V)>);
42
43impl<K: Serialize, V: Serialize> Serialize for Map<K, V> {
44 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45 where
46 S: serde::Serializer,
47 {
48 let mut map = serializer.serialize_map(Some(self.0.len()))?;
49 for (key, value) in &self.0 {
50 map.serialize_entry(key, value)?;
51 }
52 map.end()
53 }
54}
55
56#[derive(Debug, Serialize)]
57#[serde(tag = "array_difference", rename_all = "snake_case")]
58pub enum ArrayDifference {
59 PairsOnly {
61 different_pairs: Map<usize, Difference>,
63 },
64 Shorter {
66 different_pairs: Option<Map<usize, Difference>>,
68 missing_elements: Vec<serde_json::Value>,
70 },
71 Longer {
73 different_pairs: Option<Map<usize, Difference>>,
75 extra_length: usize,
77 },
78}
79
80#[derive(Debug, Serialize)]
81#[serde(rename_all = "snake_case")]
82pub enum Type {
83 Null,
84 Array,
85 Bool,
86 Object,
87 String,
88 Number,
89}
90
91#[derive(Debug, Serialize)]
92#[serde(untagged)]
93pub enum ScalarDifference {
94 Bool {
95 source: bool,
96 target: bool,
97 },
98 String {
99 source: String,
100 target: String,
101 },
102 Number {
103 source: serde_json::Number,
104 target: serde_json::Number,
105 },
106}
107
108#[derive(Debug, Serialize)]
109#[serde(tag = "difference_of", rename_all = "snake_case")]
110pub enum Difference {
111 Scalar(ScalarDifference),
112 Type {
113 source_type: Type,
114 source_value: serde_json::Value,
115 target_type: Type,
116 target_value: serde_json::Value,
117 },
118 Array(ArrayDifference),
119 Object {
120 different_entries: Map<String, EntryDifference>,
121 },
122}
123
124
125#[derive(Default, Builder, Debug)]
128pub struct Diff {
129 #[builder(setter(skip))]
130 #[builder(default = vec![].into())]
131 curr_path: Path,
132
133 #[builder(default = vec![])]
136 ignore_paths: Vec<IgnorePath>,
137
138 #[builder(default = false)]
140 equate_empty_arrays: bool,
141
142 #[builder(default = 0.0)]
145 approx_float_eq_epsilon: f64,
146
147 #[builder(default = Duration::from_millis(0))]
151 approx_date_time_eq_duration: Duration,
152
153 source: serde_json::Value,
155
156 target: serde_json::Value,
158}
159
160impl DiffBuilder {
161 pub fn ignore_path(&mut self, path: &str) -> &mut Self {
196 self.ignore_path_with_missing(path, false)
197 }
198
199 pub fn ignore_path_with_missing(&mut self, path: &str, ignore_missing: bool) -> &mut Self {
203 if let Ok(path) = Path::from_str(path) {
204 let ignore_path = IgnorePathBuilder::default()
205 .path(path)
206 .ignore_missing(ignore_missing)
207 .build()
208 .unwrap();
209 self.ignore_paths.get_or_insert_with(Vec::new).push(ignore_path);
210 }
211 self
212 }
213
214 pub fn ignore_path_with_condition(&mut self, path: &str, condition: IgnorePathCondition) -> &mut Self {
217 if let Ok(path) = Path::from_str(path) {
218 let ignore_path = IgnorePathBuilder::default()
219 .path(path)
220 .condition(condition)
221 .build()
222 .unwrap();
223 self.ignore_paths.get_or_insert_with(Vec::new).push(ignore_path);
224 }
225 self
226 }
227}
228
229impl Diff {
230 fn arrays(
231 &mut self,
232 source: Vec<serde_json::Value>,
233 target: Vec<serde_json::Value>,
234 ) -> Option<ArrayDifference> {
235 let different_pairs = self.compare_array_elements(&source, &target);
236 let different_pairs = if different_pairs.is_empty() {
237 None
238 } else {
239 Some(Map(different_pairs))
240 };
241
242 match (source.len(), target.len()) {
243 (s, t) if s > t => Some(ArrayDifference::Longer {
244 different_pairs,
245 extra_length: s - t,
246 }),
247 (s, t) if s < t => Some(ArrayDifference::Shorter {
248 different_pairs,
249 missing_elements: target.into_iter().skip(s).collect(),
250 }),
251 _ => different_pairs.map(|pairs| ArrayDifference::PairsOnly { different_pairs: pairs }),
252 }
253 }
254
255 fn compare_array_elements(
256 &mut self,
257 source: &[serde_json::Value],
258 target: &[serde_json::Value],
259 ) -> Vec<(usize, Difference)> {
260 let mut iterations = 0;
261 let res: Vec<_> = source
262 .iter()
263 .zip(target.iter())
264 .enumerate()
265 .filter_map(|(i, (s, t))| {
266 iterations += 1;
267 let elem_path = PathElement::ArrayIndex(ArrayIndex::Index(i));
268 if i > 0 { self.curr_path.pop(); }
269 self.curr_path.push(elem_path);
270 self.values(s.clone(), t.clone()).map(|diff| (i, diff))
271 })
272 .collect();
273 if iterations != 0 {
274 self.curr_path.pop();
275 };
276
277 res
278 }
279
280 #[must_use]
281 fn objects(
282 &mut self,
283 source: serde_json::Map<String, serde_json::Value>,
284 mut target: serde_json::Map<String, serde_json::Value>,
285 ) -> Option<Map<String, EntryDifference>> {
286 let mut is_first = true;
287 let mut value_differences = source
288 .into_iter()
289 .filter_map(|(key, source)| {
290 let elem_path = PathElement::Key(key.clone());
291 match is_first {
292 true => is_first = false,
293 false => { self.curr_path.pop(); }
294 }
295 self.curr_path.push(elem_path);
296
297 if self.ignore_path(target.contains_key(&key)) {
298 target.remove(&key);
299 return None;
300 }
301
302 let Some(target) = target.remove(&key) else {
303 return Some((key, EntryDifference::Extra {
304 value: source
305 }));
306 };
307
308 self.values(source, target).map(|diff| (key, EntryDifference::Value { value_diff: diff }))
309 })
310 .collect::<Vec<_>>();
311
312 if !is_first { self.curr_path.pop(); }
313
314 value_differences.extend(target.into_iter().filter_map(|(missing_key, missing_value)| {
315 let elem_path = PathElement::Key(missing_key.clone());
316 self.curr_path.push(elem_path);
317 let ignore = self.ignore_path(false);
318
319 let res = match ignore {
320 true => None,
321 false => Some((missing_key, EntryDifference::Missing {
322 value: missing_value,
323 })),
324 };
325
326 self.curr_path.pop();
327 res
328 }));
329
330 match value_differences.is_empty() {
331 true => None,
332 false => Some(Map(value_differences))
333 }
334 }
335
336 pub fn compare(mut self) -> Option<Difference> {
337 self.values(self.source.clone(), self.target.clone())
338 }
339
340 fn values(&mut self, source: serde_json::Value, target: serde_json::Value) -> Option<Difference> {
341 use serde_json::Value::{Array, Bool, Null, Number, Object, String};
342
343 match (source, target) {
344 (Null, Null) => None,
345 (Bool(source), Bool(target)) => {
346 if source == target {
347 None
348 } else {
349 Some(Difference::Scalar(ScalarDifference::Bool {
350 source,
351 target,
352 }))
353 }
354 }
355 (Number(source), Number(target)) => {
356 self.compare_numbers(source, target)
357 }
358 (String(source), String(target)) => {
359 self.compare_strings(source, target)
360 }
361 (Array(source), Array(target)) => self.arrays(source, target).map(Difference::Array),
362 (Object(source), Object(target)) => {
363 self.objects(source, target)
364 .map(|different_entries| Difference::Object { different_entries })
365 }
366 (Array(source), Null) if self.equate_empty_arrays && source.len().eq(&0) => None,
367 (Null, Array(target)) if self.equate_empty_arrays && target.len().eq(&0) => None,
368 (source, target) => {
369 Some(Difference::Type {
370 source_type: source.clone().into(),
371 source_value: source,
372 target_type: target.clone().into(),
373 target_value: target,
374 })
375 }
376 }
377 }
378
379
380 fn compare_strings(&self, source:String, target: String) -> Option<Difference> {
381 if !self.approx_date_time_eq_duration.is_zero() {
382 let source_datetime = DateTime::parse_from_rfc3339(source.as_str());
383 let target_datetime = DateTime::parse_from_rfc3339(target.as_str());
384
385 match (source_datetime, target_datetime) {
386 (Ok(source_date_time), Ok(target_date_time)) => {
387 let delta = source_date_time - target_date_time;
388 let delta = delta.abs().to_std().unwrap();
389 if delta.gt(&self.approx_date_time_eq_duration) {
390 return Some(Difference::Scalar(ScalarDifference::String {
391 source,
392 target,
393 }))
394 } else {
395 return None
396 }
397 },
398 (_, _) => {},
399 }
400 }
401 if source == target {
402 None
403 } else {
404 Some(Difference::Scalar(ScalarDifference::String {
405 source,
406 target,
407 }))
408 }
409 }
410
411 fn compare_numbers(&self, source: serde_json::Number, target: serde_json::Number) -> Option<Difference> {
412 if source.is_u64() && target.is_u64() || source.is_i64() && target.is_i64() {
413 if source == target {
414 None
415 } else {
416 Some(Difference::Scalar(ScalarDifference::Number {
417 source,
418 target,
419 }))
420 }
421 } else if source.is_f64() || target.is_f64() {
422 if relative_eq!(source.as_f64().unwrap(), target.as_f64().unwrap(), epsilon = self.approx_float_eq_epsilon) {
423 None
424 } else {
425 Some(Difference::Scalar(ScalarDifference::Number {
426 source,
427 target,
428 }))
429 }
430 } else {
431 None
432 }
433 }
434
435 fn ignore_path(&self, has_key: bool) -> bool {
442 let path = self.ignore_paths.iter().find(|p| p.path.eq(&self.curr_path));
443 let path = if let Some(path) = path {path} else {return false;};
444
445 match (path.conditions.len() > 0, path.ignore_missing, has_key) {
446 (true, _, _) => {
447 path.conditions.iter().any(|condition: &IgnorePathCondition| {
448 match condition {
449 IgnorePathCondition::Rhai(script) => {
450 let mut engine = rhai::Engine::new();
451 engine.register_fn("value_by_path", rhai_script::value_by_path);
452 let source = engine.parse_json(self.source.to_string(), true).unwrap();
453 let target = engine.parse_json(self.target.to_string(), true).unwrap();
454 let mut scope = rhai::Scope::new();
455 scope.push("source", source);
456 scope.push("target", target);
457 scope.push("curr_path", self.curr_path.clone());
458
459 let result = engine.eval_with_scope::<bool>(&mut scope, script.as_str());
460 result.unwrap_or(false)
461 }
462 }
463 })
464 },
465 (false, false, true) => true,
466 (false, true, false) => true,
467 (false, false, false) => false,
468 (false, true, true) => true,
469 }
470 }
471}
472
473impl From<serde_json::Value> for Type {
474 fn from(value: serde_json::Value) -> Self {
475 match value {
476 serde_json::Value::Null => Type::Null,
477 serde_json::Value::Bool(_) => Type::Bool,
478 serde_json::Value::Number(_) => Type::Number,
479 serde_json::Value::String(_) => Type::String,
480 serde_json::Value::Array(_) => Type::Array,
481 serde_json::Value::Object(_) => Type::Object,
482 }
483 }
484}
485
486impl PartialEq for ArrayIndex {
487 fn eq(&self, other: &Self) -> bool {
488 match (self, other) {
489 (ArrayIndex::Index(a), ArrayIndex::Index(b)) => a == b,
490 (ArrayIndex::All, ArrayIndex::Index(_)) => true,
491 (ArrayIndex::Index(_), ArrayIndex::All) => true,
492 (ArrayIndex::All, ArrayIndex::All) => true,
493 }
494 }
495}
496
497#[derive(Eq, Clone, Debug)]
498#[derive(PartialOrd, Ord)]
499pub enum ArrayIndex {
500 Index(usize),
501 All,
502}
503
504#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
505pub enum PathElement {
506 Key(String),
507 ArrayIndex(ArrayIndex),
508}
509
510#[derive(PartialEq, Clone, Debug, Builder)]
511pub struct IgnorePath {
512 path: Path,
513
514 #[builder(default = false)]
515 ignore_missing: bool,
516
517 #[builder(default = vec![])]
518 conditions: Vec<IgnorePathCondition>
519}
520
521impl IgnorePathBuilder {
522 pub fn condition(&mut self, condition: IgnorePathCondition) -> &mut Self {
523 self.conditions.get_or_insert_with(Vec::new).push(condition);
524 self
525 }
526}
527
528#[derive(Debug, Clone, PartialEq)]
529pub enum IgnorePathCondition {
530 Rhai(String)
531}
532
533#[derive(PartialEq, Clone, Debug, Default)]
534pub struct Path(Vec<PathElement>);
535
536impl Deref for Path {
537 type Target = Vec<PathElement>;
538
539 fn deref(&self) -> &Self::Target {
540 &self.0
541 }
542}
543
544impl FromIterator<PathElement> for Path {
545 fn from_iter<T: IntoIterator<Item=PathElement>>(iter: T) -> Self {
546 let mut path = Path::default();
547 for element in iter {
548 path.push(element);
549 }
550
551 path
552 }
553}
554
555impl Path {
556 fn replace_array_index_all_by_exact_path(&self, exact_path: Path) -> Option<Path> {
557 if exact_path.iter().any(|elem| {
558 match elem {
559 PathElement::ArrayIndex(ArrayIndex::All) => true,
560 _ => false
561 }
562 }) {
563 return None
564 }
565
566 let res = self.iter().zip(0..self.len()).map_while(|(elem, idx)| {
567 match elem {
568 PathElement::ArrayIndex(ArrayIndex::All) => {
569 exact_path.get(idx).cloned()
570 },
571 _ => Some(elem.clone())
572 }
573 }).collect::<Path>();
574
575 if res.len() != self.len() {
576 return None
577 }
578
579 Some(res)
580 }
581}
582
583impl DerefMut for Path {
584 fn deref_mut(&mut self) -> &mut Self::Target {
585 &mut self.0
586 }
587}
588
589impl From<Vec<PathElement>> for Path {
590 fn from(value: Vec<PathElement>) -> Self {
591 Self(value)
592 }
593}
594
595impl FromStr for Path {
596 type Err = String;
597
598 fn from_str(s: &str) -> Result<Self, Self::Err> {
599 Ok(Path(parse_element_path(s)?))
600 }
601}
602
603impl TryFrom<&str> for Path {
604 type Error = String;
605
606 fn try_from(s: &str) -> Result<Self, Self::Error> {
607 s.parse()
608 }
609}
610
611
612#[cfg(test)]
613mod tests {
614 use std::time::Duration;
615 use serde_json::json;
616 use crate::{ArrayIndex, DiffBuilder, IgnorePathCondition, Path, PathElement};
617
618 #[test]
619 fn ignore_with_rhai_condition() {
620 let obj1 = json!({
621 "users": [
622 {
623 "name": "Joe",
624 "age": 43,
625 },
626 {
627 "name": "Ana",
628 "age": 33,
629 "animals": {
630 "type": "dog"
631 }
632 },
633 ]
634 });
635
636 let obj2 = json!({
637 "users": [
638 {
639 "name": "Joe",
640 "age": 43,
641 },
642 {
643 "name": "Ana",
644 "age": 33,
645 "animals": {
646 "type": "cat"
647 }
648 },
649 ]
650 });
651
652 let script = r#"
653 let res = target.value_by_path("users.[_].age", curr_path);
654 res == 33
655 "#;
656
657 let diff = DiffBuilder::default()
658 .source(obj1)
659 .target(obj2)
660 .ignore_path_with_condition("users.[_].animals.type", IgnorePathCondition::Rhai(script.to_string()))
661 .build();
662 let diff = diff.unwrap().compare();
663
664 assert!(diff.is_none(), "{:?}", diff);
665 }
666
667 #[test]
668 fn ignore_source_missing() {
669 let obj1 = json!({
670 "name": "John Doe",
671 });
672
673 let obj2 = json!({
674 "name": "John Doe",
675 "age": 30
676 });
677
678 let diff = DiffBuilder::default()
679 .source(obj1)
680 .target(obj2)
681 .ignore_path_with_missing("age", true)
682 .build();
683 let diff = diff.unwrap().compare();
684
685 assert!(diff.is_none(), "{:?}", diff);
686 }
687
688 #[test]
689 fn equal_objects() {
690 let obj1 = json!({
691 "string": "b",
692 "int": 1,
693 "float": 1.0,
694 "bool": true,
695 "int_array": [1, 2, 3],
696 "float_array": [1.0, 2.0, 3.0],
697 "bool_array": [true, false, false],
698 "string_array": ["foo", "bar"],
699 "empty_array": [],
700 "null": null,
701 "object": {
702 "string": "c",
703 "int": 1,
704 "float": 1.0,
705 "bool": true,
706 "array": [1, 2, 3],
707 "null": null,
708 "object": {
709 "string": "d",
710 }
711 },
712 });
713
714 let obj2 = json!({
715 "string": "b",
716 "int": 1,
717 "float": 1.0,
718 "bool": true,
719 "int_array": [1, 2, 3],
720 "float_array": [1.0, 2.0, 3.0],
721 "bool_array": [true, false, false],
722 "string_array": ["foo", "bar"],
723 "empty_array": [],
724 "null": null,
725 "object": {
726 "string": "c",
727 "int": 1,
728 "float": 1.0,
729 "bool": true,
730 "array": [1, 2, 3],
731 "null": null,
732 "object": {
733 "string": "d",
734 }
735 },
736 });
737
738 let diff = DiffBuilder::default().source(obj1).target(obj2).build().unwrap();
739 let diff = diff.compare();
740 assert_eq!(true, diff.is_none(), "diff should be None, but got: {:?}", diff);
741 }
742
743
744 #[test]
745 fn ignore_fields() {
746 let user_1 = json!({
747 "user": "John",
748 "address": {
749 "city": "Astana",
750 "zip": 123,
751 },
752 "animals": ["dog", "cat"],
753 "object_array": [{"a": "b", "c": "d"}],
754 "optional_array": [],
755 "target_missing_value": 1,
756 });
757
758 let user_2 = json!({
759 "user": "Joe",
760 "address": {
761 "city": "Boston",
762 "zip": 312,
763 },
764 "animals": ["dog", "cat"],
765 "object_array": [{"a": "3", "c": "d"}],
766 "optional_array": null,
767 });
768
769 let diff = DiffBuilder::default()
770 .ignore_path("user")
771 .ignore_path("address.city")
772 .ignore_path("address.zip")
773 .ignore_path("object_array.[_].a")
774 .ignore_path_with_missing("target_missing_value", true)
775 .equate_empty_arrays(true)
776 .source(user_1)
777 .target(user_2)
778 .build()
779 .unwrap();
780
781 let diff = diff.compare();
782
783 assert_eq!(true, diff.is_none(), "diff should be None, but got: {:?}", diff);
784 }
785
786 #[test]
787 fn approx_float_eq() {
788 let obj1 = json!({
789 "float": 1.34
790 });
791
792 let obj2 = json!({
793 "float": 1.341
794 });
795
796 let diff = DiffBuilder::default()
797 .approx_float_eq_epsilon(0.001)
798 .source(obj1).target(obj2).build().unwrap();
799
800 let diff = diff.compare();
801
802 assert_eq!(true, diff.is_none(), "diff should be None, but got: {:?}", diff);
803 }
804
805 #[test]
806 fn approx_date_time_eq() {
807 let obj1 = json!({
808 "ts": "2023-07-25T15:30:01Z"
809 });
810
811 let obj2 = json!({
812 "ts": "2023-07-25T15:30:00Z"
813 });
814
815 let diff = DiffBuilder::default()
816 .approx_date_time_eq_duration(Duration::from_secs(1))
817 .source(obj1).target(obj2).build().unwrap();
818
819 let diff = diff.compare();
820
821 assert_eq!(true, diff.is_none(), "diff should be None, but got: {:?}", diff);
822 }
823
824 #[test]
825 fn test_replace_array_index_all_by_exact_path() {
826 let pattern_path: Path = vec![
827 PathElement::Key("users".to_string()),
828 PathElement::ArrayIndex(ArrayIndex::All),
829 PathElement::Key("address".to_string()),
830 ].into();
831
832 let exact_path: Path = vec![
833 PathElement::Key("users".to_string()),
834 PathElement::ArrayIndex(ArrayIndex::Index(1)),
835 PathElement::Key("name".to_string()),
836 ].into();
837
838 let actual = pattern_path.replace_array_index_all_by_exact_path(exact_path);
839 let expected: Path = vec![
840 PathElement::Key("users".to_string()),
841 PathElement::ArrayIndex(ArrayIndex::Index(1)),
842 PathElement::Key("address".to_string()),
843 ].into();
844 assert!(actual.is_some());
845 assert_eq!(actual.unwrap(), expected);
846 }
847}