Skip to content

Commit 2be95f9

Browse files
authored
Merge pull request #3 from B-2U/main
Introduced owned Difference
2 parents 13a0b78 + 32dc182 commit 2be95f9

File tree

4 files changed

+170
-50
lines changed

4 files changed

+170
-50
lines changed

.github/workflows/rust-ci.yml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ jobs:
1212
name: Lint
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v2
16-
- uses: actions-rs/toolchain@v1
15+
- uses: actions/checkout@v4
16+
- uses: dtolnay/rust-toolchain@stable
1717
with:
1818
toolchain: stable
19-
override: true
2019

2120
# make sure all code has been formatted with rustfmt
2221
- name: check rustfmt
@@ -39,10 +38,9 @@ jobs:
3938
runs-on: ${{ matrix.os }}
4039
steps:
4140
- uses: actions/checkout@v2
42-
- uses: actions-rs/toolchain@v1
41+
- uses: dtolnay/rust-toolchain@stable
4342
with:
4443
toolchain: stable
45-
override: true
4644
- run: cargo fetch
4745
- name: cargo test build
4846
# Note the use of release here means longer compile time, but much
@@ -57,10 +55,9 @@ jobs:
5755
runs-on: ubuntu-latest
5856
steps:
5957
- uses: actions/checkout@v2
60-
- uses: actions-rs/toolchain@v1
58+
- uses: dtolnay/rust-toolchain@stable
6159
with:
6260
toolchain: stable
63-
override: true
6461
- run: cargo fetch
6562
- name: cargo publish check
6663
run: cargo publish --dry-run
@@ -73,11 +70,10 @@ jobs:
7370
runs-on: ubuntu-latest
7471
if: startsWith(github.ref, 'refs/tags/')
7572
steps:
76-
- uses: actions/checkout@v1
77-
- uses: actions-rs/toolchain@v1
73+
- uses: actions/checkout@v4
74+
- uses: dtolnay/rust-toolchain@stable
7875
with:
7976
toolchain: stable
80-
override: true
8177
- run: cargo fetch
8278
- run: cargo publish
8379
env:

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
- Expose the types `Difference`, `Path` and `Key`.
11+
- New function `try_assert_json_matches()` which returns a `Vec<Difference>` instead of a error message, allow user to do further processing.
12+
1013
## [0.2.1] - 2025-03-11
1114

1215
### Fixed

src/diff.rs

Lines changed: 96 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@ use float_cmp::{ApproxEq, F64Margin};
44
use serde_json::Value;
55
use std::{collections::HashSet, fmt};
66

7-
pub(crate) fn diff<'a>(lhs: &'a Value, rhs: &'a Value, config: &'a Config) -> Vec<Difference<'a>> {
7+
pub(crate) fn diff<'a>(
8+
lhs: &'a Value,
9+
rhs: &'a Value,
10+
config: &'a Config,
11+
) -> Vec<DifferenceRef<'a>> {
812
let mut acc = vec![];
9-
diff_with(lhs, rhs, config, Path::Root, &mut acc);
13+
diff_with(lhs, rhs, config, PathRef::Root, &mut acc);
1014
acc
1115
}
1216

1317
fn diff_with<'a>(
1418
lhs: &'a Value,
1519
rhs: &'a Value,
1620
config: &'a Config,
17-
path: Path<'a>,
18-
acc: &mut Vec<Difference<'a>>,
21+
path: PathRef<'a>,
22+
acc: &mut Vec<DifferenceRef<'a>>,
1923
) {
2024
let mut folder = DiffFolder {
2125
rhs,
@@ -30,16 +34,16 @@ fn diff_with<'a>(
3034
#[derive(Debug)]
3135
struct DiffFolder<'a, 'b> {
3236
rhs: &'a Value,
33-
path: Path<'a>,
34-
acc: &'b mut Vec<Difference<'a>>,
37+
path: PathRef<'a>,
38+
acc: &'b mut Vec<DifferenceRef<'a>>,
3539
config: &'a Config,
3640
}
3741

3842
macro_rules! direct_compare {
3943
($name:ident) => {
4044
fn $name(&mut self, lhs: &'a Value) {
4145
if self.rhs != lhs {
42-
self.acc.push(Difference {
46+
self.acc.push(DifferenceRef {
4347
lhs: Some(lhs),
4448
rhs: Some(&self.rhs),
4549
path: self.path.clone(),
@@ -64,7 +68,7 @@ impl<'a> DiffFolder<'a, '_> {
6468
},
6569
};
6670
if !is_equal {
67-
self.acc.push(Difference {
71+
self.acc.push(DifferenceRef {
6872
lhs: Some(lhs),
6973
rhs: Some(self.rhs),
7074
path: self.path.clone(),
@@ -101,7 +105,7 @@ impl<'a> DiffFolder<'a, '_> {
101105
let rhs_len = rhs.len();
102106

103107
if self.config.compare_mode == CompareMode::Strict && lhs_len != rhs_len {
104-
self.acc.push(Difference {
108+
self.acc.push(DifferenceRef {
105109
lhs: Some(lhs),
106110
rhs: Some(self.rhs),
107111
path: self.path.clone(),
@@ -124,7 +128,7 @@ impl<'a> DiffFolder<'a, '_> {
124128
.filter(|lhs_item| diff(lhs_item, rhs_item, self.config).is_empty())
125129
.count();
126130
if lhs_matching_items_count < rhs_item_count {
127-
self.acc.push(Difference {
131+
self.acc.push(DifferenceRef {
128132
lhs: Some(lhs),
129133
rhs: Some(self.rhs),
130134
path: self.path.clone(),
@@ -134,7 +138,7 @@ impl<'a> DiffFolder<'a, '_> {
134138
}
135139
}
136140
} else {
137-
self.acc.push(Difference {
141+
self.acc.push(DifferenceRef {
138142
lhs: Some(lhs),
139143
rhs: Some(self.rhs),
140144
path: self.path.clone(),
@@ -154,12 +158,12 @@ impl<'a> DiffFolder<'a, '_> {
154158
match self.config.compare_mode {
155159
CompareMode::Inclusive => {
156160
for (idx, rhs) in rhs.iter().enumerate() {
157-
let path = self.path.append(Key::Idx(idx));
161+
let path = self.path.append(KeyRef::Idx(idx));
158162

159163
if let Some(lhs) = lhs.get(idx) {
160164
diff_with(lhs, rhs, self.config, path, self.acc)
161165
} else {
162-
self.acc.push(Difference {
166+
self.acc.push(DifferenceRef {
163167
lhs: None,
164168
rhs: Some(self.rhs),
165169
path,
@@ -175,22 +179,22 @@ impl<'a> DiffFolder<'a, '_> {
175179
.chain(lhs.indexes())
176180
.collect::<HashSet<_>>();
177181
for key in all_keys {
178-
let path = self.path.append(Key::Idx(key));
182+
let path = self.path.append(KeyRef::Idx(key));
179183

180184
match (lhs.get(key), rhs.get(key)) {
181185
(Some(lhs), Some(rhs)) => {
182186
diff_with(lhs, rhs, self.config, path, self.acc);
183187
}
184188
(None, Some(rhs)) => {
185-
self.acc.push(Difference {
189+
self.acc.push(DifferenceRef {
186190
lhs: None,
187191
rhs: Some(rhs),
188192
path,
189193
config: self.config.clone(),
190194
});
191195
}
192196
(Some(lhs), None) => {
193-
self.acc.push(Difference {
197+
self.acc.push(DifferenceRef {
194198
lhs: Some(lhs),
195199
rhs: None,
196200
path,
@@ -205,7 +209,7 @@ impl<'a> DiffFolder<'a, '_> {
205209
}
206210
}
207211
} else {
208-
self.acc.push(Difference {
212+
self.acc.push(DifferenceRef {
209213
lhs: Some(lhs),
210214
rhs: Some(self.rhs),
211215
path: self.path.clone(),
@@ -221,12 +225,12 @@ impl<'a> DiffFolder<'a, '_> {
221225
match self.config.compare_mode {
222226
CompareMode::Inclusive => {
223227
for (key, rhs) in rhs.iter() {
224-
let path = self.path.append(Key::Field(key));
228+
let path = self.path.append(KeyRef::Field(key));
225229

226230
if let Some(lhs) = lhs.get(key) {
227231
diff_with(lhs, rhs, self.config, path, self.acc)
228232
} else {
229-
self.acc.push(Difference {
233+
self.acc.push(DifferenceRef {
230234
lhs: None,
231235
rhs: Some(self.rhs),
232236
path,
@@ -238,22 +242,22 @@ impl<'a> DiffFolder<'a, '_> {
238242
CompareMode::Strict => {
239243
let all_keys = rhs.keys().chain(lhs.keys()).collect::<HashSet<_>>();
240244
for key in all_keys {
241-
let path = self.path.append(Key::Field(key));
245+
let path = self.path.append(KeyRef::Field(key));
242246

243247
match (lhs.get(key), rhs.get(key)) {
244248
(Some(lhs), Some(rhs)) => {
245249
diff_with(lhs, rhs, self.config, path, self.acc);
246250
}
247251
(None, Some(rhs)) => {
248-
self.acc.push(Difference {
252+
self.acc.push(DifferenceRef {
249253
lhs: None,
250254
rhs: Some(rhs),
251255
path,
252256
config: self.config.clone(),
253257
});
254258
}
255259
(Some(lhs), None) => {
256-
self.acc.push(Difference {
260+
self.acc.push(DifferenceRef {
257261
lhs: Some(lhs),
258262
rhs: None,
259263
path,
@@ -268,7 +272,7 @@ impl<'a> DiffFolder<'a, '_> {
268272
}
269273
}
270274
} else {
271-
self.acc.push(Difference {
275+
self.acc.push(DifferenceRef {
272276
lhs: Some(lhs),
273277
rhs: Some(self.rhs),
274278
path: self.path.clone(),
@@ -278,15 +282,35 @@ impl<'a> DiffFolder<'a, '_> {
278282
}
279283
}
280284

285+
/// Represents a difference between two JSON values.
286+
#[derive(Debug, PartialEq, Clone)]
287+
pub struct Difference {
288+
path: Path,
289+
lhs: Option<Value>,
290+
rhs: Option<Value>,
291+
config: Config,
292+
}
293+
294+
impl<'a> From<DifferenceRef<'a>> for Difference {
295+
fn from(diff: DifferenceRef<'a>) -> Self {
296+
Difference {
297+
path: Path::from(diff.path),
298+
lhs: diff.lhs.cloned(),
299+
rhs: diff.rhs.cloned(),
300+
config: diff.config.clone(),
301+
}
302+
}
303+
}
304+
281305
#[derive(Debug, PartialEq)]
282-
pub(crate) struct Difference<'a> {
283-
path: Path<'a>,
306+
pub(crate) struct DifferenceRef<'a> {
307+
path: PathRef<'a>,
284308
lhs: Option<&'a Value>,
285309
rhs: Option<&'a Value>,
286310
config: Config,
287311
}
288312

289-
impl fmt::Display for Difference<'_> {
313+
impl fmt::Display for DifferenceRef<'_> {
290314
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291315
let json_to_string = |json: &Value| serde_json::to_string_pretty(json).unwrap();
292316

@@ -330,30 +354,46 @@ impl fmt::Display for Difference<'_> {
330354
}
331355
}
332356

357+
/// Represents a path to a JSON value in a tree structure.
333358
#[derive(Debug, Clone, PartialEq)]
334-
enum Path<'a> {
359+
enum Path {
335360
Root,
336-
Keys(Vec<Key<'a>>),
361+
Keys(Vec<Key>),
337362
}
338363

339-
impl<'a> Path<'a> {
340-
fn append(&self, next: Key<'a>) -> Path<'a> {
364+
impl<'a> From<PathRef<'a>> for Path {
365+
fn from(path: PathRef<'a>) -> Self {
366+
match path {
367+
PathRef::Root => Path::Root,
368+
PathRef::Keys(keys) => Path::Keys(keys.into_iter().map(Key::from).collect()),
369+
}
370+
}
371+
}
372+
373+
#[derive(Debug, Clone, PartialEq)]
374+
enum PathRef<'a> {
375+
Root,
376+
Keys(Vec<KeyRef<'a>>),
377+
}
378+
379+
impl<'a> PathRef<'a> {
380+
fn append(&self, next: KeyRef<'a>) -> PathRef<'a> {
341381
match self {
342-
Path::Root => Path::Keys(vec![next]),
343-
Path::Keys(list) => {
382+
PathRef::Root => PathRef::Keys(vec![next]),
383+
PathRef::Keys(list) => {
344384
let mut copy = list.clone();
345385
copy.push(next);
346-
Path::Keys(copy)
386+
PathRef::Keys(copy)
347387
}
348388
}
349389
}
350390
}
351391

352-
impl fmt::Display for Path<'_> {
392+
impl fmt::Display for PathRef<'_> {
353393
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
354394
match self {
355-
Path::Root => write!(f, "(root)"),
356-
Path::Keys(keys) => {
395+
PathRef::Root => write!(f, "(root)"),
396+
PathRef::Keys(keys) => {
357397
for key in keys {
358398
write!(f, "{}", key)?;
359399
}
@@ -363,17 +403,33 @@ impl fmt::Display for Path<'_> {
363403
}
364404
}
365405

406+
/// Represents a key in a JSON object or an index in a JSON array.
407+
#[derive(Debug, Clone, PartialEq, Eq)]
408+
enum Key {
409+
Idx(usize),
410+
Field(String),
411+
}
412+
413+
impl<'a> From<KeyRef<'a>> for Key {
414+
fn from(key: KeyRef<'a>) -> Self {
415+
match key {
416+
KeyRef::Idx(idx) => Key::Idx(idx),
417+
KeyRef::Field(field) => Key::Field(field.to_owned()),
418+
}
419+
}
420+
}
421+
366422
#[derive(Debug, Copy, Clone, PartialEq)]
367-
enum Key<'a> {
423+
enum KeyRef<'a> {
368424
Idx(usize),
369425
Field(&'a str),
370426
}
371427

372-
impl fmt::Display for Key<'_> {
428+
impl fmt::Display for KeyRef<'_> {
373429
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
374430
match self {
375-
Key::Idx(idx) => write!(f, "[{}]", idx),
376-
Key::Field(key) => write!(f, ".{}", key),
431+
KeyRef::Idx(idx) => write!(f, "[{}]", idx),
432+
KeyRef::Field(key) => write!(f, ".{}", key),
377433
}
378434
}
379435
}

0 commit comments

Comments
 (0)