Skip to main content

cool_diff/
model.rs

1use serde_json::Value;
2
3/// The top-level result of a diff operation.
4pub struct DiffTree {
5    /// The root-level diff nodes.
6    pub roots: Vec<DiffNode>,
7}
8
9impl DiffTree {
10    /// Returns `true` if there are no differences.
11    pub fn is_empty(&self) -> bool {
12        self.roots.is_empty()
13    }
14}
15
16/// Describes what kind of children a container holds.
17///
18/// Used to select the correct unit word for omitted count markers
19/// (e.g. "fields" vs "items").
20pub enum ChildKind {
21    /// Children are object fields (key-value pairs).
22    Fields,
23
24    /// Children are array items.
25    Items,
26}
27
28impl DiffNode {
29    /// Creates a leaf node representing a single difference.
30    pub fn leaf(segment: PathSegment, kind: DiffKind) -> Self {
31        Self::Leaf { segment, kind }
32    }
33
34    /// Creates a container node with child diffs.
35    pub fn container(
36        segment: PathSegment,
37        child_kind: ChildKind,
38        omitted_count: u16,
39        children: Vec<DiffNode>,
40    ) -> Self {
41        Self::Container {
42            segment,
43            child_kind,
44            omitted_count,
45            children,
46        }
47    }
48}
49
50/// A node in the diff tree.
51pub enum DiffNode {
52    /// An intermediate node containing child diffs.
53    Container {
54        /// The path segment for this container.
55        segment: PathSegment,
56
57        /// Whether the children are object fields or array items.
58        child_kind: ChildKind,
59
60        /// Number of siblings/elements in the actual data not shown in the diff.
61        omitted_count: u16,
62
63        /// Child diff nodes.
64        children: Vec<DiffNode>,
65    },
66
67    /// A terminal node representing a single difference.
68    Leaf {
69        /// The path segment for this leaf.
70        segment: PathSegment,
71        /// The kind of difference.
72        kind: DiffKind,
73    },
74}
75
76impl DiffKind {
77    /// Creates a `Changed` diff for values of the same type that differ.
78    pub fn changed(actual: Value, expected: Value) -> Self {
79        Self::Changed { actual, expected }
80    }
81
82    /// Creates a `Missing` diff for a value not found in actual.
83    pub fn missing(expected: Value) -> Self {
84        Self::Missing { expected }
85    }
86
87    /// Creates a `TypeMismatch` diff for values with different JSON types.
88    pub fn type_mismatch(
89        actual: Value,
90        actual_type: &'static str,
91        expected: Value,
92        expected_type: &'static str,
93    ) -> Self {
94        Self::TypeMismatch {
95            actual,
96            actual_type,
97            expected,
98            expected_type,
99        }
100    }
101}
102
103/// The kind of difference found at a leaf node.
104pub enum DiffKind {
105    /// Values differ but have the same type.
106    Changed {
107        /// The value that was actually present.
108        actual: Value,
109
110        /// The value that was expected.
111        expected: Value,
112    },
113
114    /// A key or element is missing from the actual data.
115    Missing {
116        /// The expected value that was not found.
117        expected: Value,
118    },
119
120    /// Values have different JSON types.
121    TypeMismatch {
122        /// The value that was actually present.
123        actual: Value,
124
125        /// Human-readable name of the actual type.
126        actual_type: &'static str,
127
128        /// The value that was expected.
129        expected: Value,
130
131        /// Human-readable name of the expected type.
132        expected_type: &'static str,
133    },
134}
135
136impl PathSegment {
137    /// Returns true if this segment represents an array element.
138    pub fn is_array(&self) -> bool {
139        matches!(
140            self,
141            PathSegment::NamedElement { .. } | PathSegment::Index(_) | PathSegment::Unmatched
142        )
143    }
144}
145
146/// A segment in the path to a diff location.
147pub enum PathSegment {
148    /// An object key (e.g. `spec` in `spec.containers`).
149    Key(String),
150
151    /// An array element matched by a distinguished key (e.g. `name: FOO`).
152    NamedElement {
153        /// The key used to match (e.g. `name`).
154        match_key: String,
155
156        /// The value of the match key (e.g. `FOO`).
157        match_value: String,
158    },
159
160    /// An array element matched by position.
161    Index(u16),
162
163    /// An expected array element with no matching actual element.
164    ///
165    /// Used with key-based and contains matching when no candidate was
166    /// found in the actual array.
167    Unmatched,
168}