1#![doc = include_str!("../README.md")]
2use serde::{ser::SerializeMap, Serialize};
3
4#[derive(Debug, Serialize)]
5#[serde(tag = "entry_difference", rename_all = "snake_case")]
6pub enum EntryDifference {
7 Missing { value: serde_json::Value },
9 Extra,
11 Value { value_diff: Difference },
13}
14
15#[derive(Debug)]
16pub struct DumbMap<K: Serialize, V: Serialize>(pub Vec<(K, V)>);
17
18impl<K: Serialize, V: Serialize> Serialize for DumbMap<K, V> {
19 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
20 where
21 S: serde::Serializer,
22 {
23 let mut map = serializer.serialize_map(Some(self.0.len()))?;
24 for (key, value) in &self.0 {
25 map.serialize_entry(key, value)?;
26 }
27 map.end()
28 }
29}
30
31#[derive(Debug, Serialize)]
32#[serde(tag = "array_difference", rename_all = "snake_case")]
33pub enum ArrayDifference {
34 PairsOnly {
36 different_pairs: DumbMap<usize, Difference>,
38 },
39 Shorter {
41 different_pairs: Option<DumbMap<usize, Difference>>,
43 missing_elements: Vec<serde_json::Value>,
45 },
46 Longer {
48 different_pairs: Option<DumbMap<usize, Difference>>,
50 extra_length: usize,
52 },
53}
54
55#[derive(Debug, Serialize)]
56#[serde(rename_all = "snake_case")]
57pub enum Type {
58 Null,
59 Array,
60 Bool,
61 Object,
62 String,
63 Number,
64}
65
66#[derive(Debug, Serialize)]
67#[serde(untagged)]
68pub enum ScalarDifference {
69 Bool {
70 source: bool,
71 target: bool,
72 },
73 String {
74 source: String,
75 target: String,
76 },
77 Number {
78 source: serde_json::Number,
79 target: serde_json::Number,
80 },
81}
82
83#[derive(Debug, Serialize)]
84#[serde(tag = "difference_of", rename_all = "snake_case")]
85pub enum Difference {
86 Scalar(ScalarDifference),
87 Type {
88 source_type: Type,
89 target_type: Type,
90 target_value: serde_json::Value,
91 },
92 Array(ArrayDifference),
93 Object {
94 different_entries: DumbMap<String, EntryDifference>,
95 },
96}
97
98#[must_use]
99pub fn arrays(
101 source: Vec<serde_json::Value>,
102 target: Vec<serde_json::Value>,
103) -> Option<ArrayDifference> {
104 let mut source_iter = source.into_iter().enumerate().peekable();
105 let mut target_iter = target.into_iter().peekable();
106
107 let mut different_pairs = vec![];
108 while let (Some(_), Some(_)) = (source_iter.peek(), target_iter.peek()) {
109 let (Some((i, source)), Some(target)) = (source_iter.next(), target_iter.next()) else {
110 unreachable!("checked by peek()");
111 };
112 different_pairs.push(values(source, target).map(|diff| (i, diff)));
113 }
114 let different_pairs = different_pairs.into_iter().flatten().collect::<Vec<_>>();
115 let different_pairs = if different_pairs.is_empty() {
116 None
117 } else {
118 Some(DumbMap(different_pairs))
119 };
120
121 let extra_elements = source_iter.map(|(_, source)| source).collect::<Vec<_>>();
122 let missing_elements = target_iter.collect::<Vec<_>>();
123
124 if !extra_elements.is_empty() {
125 return Some(ArrayDifference::Longer {
126 different_pairs,
127 extra_length: extra_elements.len(),
128 });
129 }
130
131 if !missing_elements.is_empty() {
132 return Some(ArrayDifference::Shorter {
133 different_pairs,
134 missing_elements,
135 });
136 }
137
138 different_pairs.map(|different_pairs| ArrayDifference::PairsOnly { different_pairs })
139}
140
141#[must_use]
142pub fn objects(
143 source: serde_json::Map<String, serde_json::Value>,
144 mut target: serde_json::Map<String, serde_json::Value>,
145) -> Option<DumbMap<String, EntryDifference>> {
146 let mut value_differences = source
147 .into_iter()
148 .filter_map(|(key, source)| {
149 let Some(target) = target.remove(&key) else {
150 return Some((key, EntryDifference::Extra));
151 };
152
153 values(source, target).map(|diff| (key, EntryDifference::Value { value_diff: diff }))
154 })
155 .collect::<Vec<_>>();
156
157 value_differences.extend(target.into_iter().map(|(missing_key, missing_value)| {
158 (
159 missing_key,
160 EntryDifference::Missing {
161 value: missing_value,
162 },
163 )
164 }));
165
166 if value_differences.is_empty() {
167 None
168 } else {
169 Some(DumbMap(value_differences))
170 }
171}
172
173impl From<serde_json::Value> for Type {
174 fn from(value: serde_json::Value) -> Self {
175 match value {
176 serde_json::Value::Null => Type::Null,
177 serde_json::Value::Bool(_) => Type::Bool,
178 serde_json::Value::Number(_) => Type::Number,
179 serde_json::Value::String(_) => Type::String,
180 serde_json::Value::Array(_) => Type::Array,
181 serde_json::Value::Object(_) => Type::Object,
182 }
183 }
184}
185
186#[must_use]
187pub fn values(source: serde_json::Value, target: serde_json::Value) -> Option<Difference> {
188 use serde_json::Value::{Array, Bool, Null, Number, Object, String};
189
190 match (source, target) {
191 (Null, Null) => None,
192 (Bool(source), Bool(target)) => {
193 if source == target {
194 None
195 } else {
196 Some(Difference::Scalar(ScalarDifference::Bool {
197 source,
198 target,
199 }))
200 }
201 }
202 (Number(source), Number(target)) => {
203 if source == target {
204 None
205 } else {
206 Some(Difference::Scalar(ScalarDifference::Number {
207 source,
208 target,
209 }))
210 }
211 }
212 (String(source), String(target)) => {
213 if source == target {
214 None
215 } else {
216 Some(Difference::Scalar(ScalarDifference::String {
217 source,
218 target,
219 }))
220 }
221 }
222 (Array(source), Array(target)) => arrays(source, target).map(Difference::Array),
223 (Object(source), Object(target)) => objects(source, target)
224 .map(|different_entries| Difference::Object { different_entries }),
225 (source, target) => Some(Difference::Type {
226 source_type: source.into(),
227 target_type: target.clone().into(),
228 target_value: target,
229 }),
230 }
231}