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}