sjdiff/
lib.rs

1//! # Structural JSON Diff Library for Rust
2//!
3//! `sjdiff` – is a library for Rust that compares two JSON values and produces a structural difference between them.
4//!
5//! ## Examples
6//!
7//! **Compare two objects**
8//!
9//! ```rust
10#![doc = include_str!("../examples/simple_object_diff.rs")]
11//! ```
12//!
13//! Output:
14//! ```json
15#![doc = include_str!("../examples/simple_object_diff.json")]
16//! ```
17mod 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    /// An entry from `target` that `source` is missing
33    Missing { value: serde_json::Value },
34    /// An entry that `source` has, and `target` doesn't
35    Extra { value: serde_json::Value },
36    /// The entry exists in both JSONs, but the values are different
37    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    /// `source` and `target` are the same length, but some values of the same indices are different
60    PairsOnly {
61        /// differing pairs that appear in the overlapping indices of `source` and `target`
62        different_pairs: Map<usize, Difference>,
63    },
64    /// `source` is shorter than `target`
65    Shorter {
66        /// differing pairs that appear in the overlapping indices of `source` and `target`
67        different_pairs: Option<Map<usize, Difference>>,
68        /// elements missing in `source` that appear in `target`
69        missing_elements: Vec<serde_json::Value>,
70    },
71    /// `source` is longer than `target`
72    Longer {
73        /// differing pairs that appear in the overlapping indices of `source` and `target`
74        different_pairs: Option<Map<usize, Difference>>,
75        /// The amount of extra elements `source` has that `target` does not
76        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/// Use [`DiffBuilder`] to build [`Diff`] first and run [`Diff::compare`] to get the
126/// difference between two JSON values.
127#[derive(Default, Builder, Debug)]
128pub struct Diff {
129    #[builder(setter(skip))]
130    #[builder(default = vec![].into())]
131    curr_path: Path,
132
133    /// An array of paths to ignore.
134    /// Use [`DiffBuilder::ignore_path`] to add them in a more convenient way.
135    #[builder(default = vec![])]
136    ignore_paths: Vec<IgnorePath>,
137
138    /// If true arrays with a length of zero will be equal, regardless of whether they are nil.
139    #[builder(default = false)]
140    equate_empty_arrays: bool,
141
142    /// If not zero a float comparison will be done using [`approx::relative_eq`].
143    /// It's useful when you want to ignore small differences, e.g. `0.19999999999999 ~ 0.2`.
144    #[builder(default = 0.0)]
145    approx_float_eq_epsilon: f64,
146
147    /// An acceptable duration difference for the JSON string values that
148    /// are valid timestamps. Date approximation will only be executed
149    /// when this value is not zero and a string value is a valid `rfc3339` date.
150    #[builder(default = Duration::from_millis(0))]
151    approx_date_time_eq_duration: Duration,
152
153    /// Source JSON value that will be compared with [`Diff::target`].
154    source: serde_json::Value,
155
156    /// Target JSON value that will be compared with [`Diff::source`].
157    target: serde_json::Value,
158}
159
160impl DiffBuilder {
161    /// Set a JSON path using a string format that you want to ignore during the comparison.
162    /// A string path will be parsed to [`IgnorePath`] and appended to [`Diff::ignore_paths`].
163    ///
164    /// ## Examples
165    ///
166    /// `a.[_].c` will ignore `c` key in any element of array `a`:
167    ///
168    /// ```json
169    /// {
170    ///     "a": [{"b": "3", "c": "4"}]
171    /// }
172    /// ```
173    /// `[_]` means any index.
174    ///
175    /// and `a.[1].c` will ignore `c` key in the element with index 1.
176    ///
177    /// `address.zip` will ignore `zip` key in the `address`:
178    ///
179    /// ```json
180    /// {
181    ///     "address": {
182    //         "city": "Astana",
183    //         "zip": 123,
184    //      }
185    /// }
186    /// ```
187    ///
188    /// <div class="warning">
189    ///
190    /// **NOTE**: if the element is missing in the source or target and you added
191    ///  it to ignore paths, the result diff will still show it as a missing one.
192    ///  Use [`DiffBuilder::ignore_path_with_missing`] with `ignore_missing` set to true instead.
193    ///
194    /// </div>
195    pub fn ignore_path(&mut self, path: &str) -> &mut Self {
196        self.ignore_path_with_missing(path, false)
197    }
198
199    /// Adds a path to the ignored ones. `ignore_missing` indicates whether the element should
200    /// be ignored if it's missing in the source or target.
201    /// See documentation for [`DiffBuilder::ignore_path`] for usage examples.
202    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    /// Does the same as [`DiffBuilder::ignore_path`] but you can pass a custom script as a condition.
215    /// See the example `ignore_with_rhai_script.rs` to learn how to use it.
216    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    /// Returns true if the current path should be ignored.
436    /// `has_key` indicates if the opposite object has the key.
437    /// So, if the function is called when the keys of source are iterated
438    /// target should be checked for key existence.
439    /// After it can only be called on vector of target keys, which
440    /// means that all those keys are missing on the source.
441    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}