Skip to content
This repository was archived by the owner on Jun 8, 2025. It is now read-only.

Commit 6b33b82

Browse files
committed
expose Difference, introduce owned types DifferenceBuf PathBuf KeyBuf
1 parent b5dc604 commit 6b33b82

File tree

3 files changed

+110
-12
lines changed

3 files changed

+110
-12
lines changed

src/diff.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ impl<'a, 'b> DiffFolder<'a, 'b> {
201201
}
202202

203203
#[derive(Debug, PartialEq)]
204-
pub(crate) struct Difference<'a> {
204+
pub struct Difference<'a> {
205205
path: Path<'a>,
206206
lhs: Option<&'a Value>,
207207
rhs: Option<&'a Value>,
@@ -252,6 +252,32 @@ impl<'a> fmt::Display for Difference<'a> {
252252
}
253253
}
254254

255+
impl Difference<'_> {
256+
/// Returns a string representation of the difference, suitable for display.
257+
pub fn display(&self) -> String {
258+
self.to_string()
259+
}
260+
}
261+
262+
#[derive(Debug, PartialEq)]
263+
pub struct DifferenceBuf {
264+
path: PathBuf,
265+
lhs: Option<Value>,
266+
rhs: Option<Value>,
267+
config: Config,
268+
}
269+
270+
impl<'a> From<Difference<'a>> for DifferenceBuf {
271+
fn from(diff: Difference<'a>) -> Self {
272+
DifferenceBuf {
273+
path: PathBuf::from(diff.path),
274+
lhs: diff.lhs.cloned(),
275+
rhs: diff.rhs.cloned(),
276+
config: diff.config.clone(),
277+
}
278+
}
279+
}
280+
255281
#[derive(Debug, Clone, PartialEq)]
256282
enum Path<'a> {
257283
Root,
@@ -285,12 +311,41 @@ impl<'a> fmt::Display for Path<'a> {
285311
}
286312
}
287313

314+
#[derive(Debug, Clone, PartialEq)]
315+
enum PathBuf {
316+
Root,
317+
Keys(Vec<KeyBuf>),
318+
}
319+
320+
impl<'a> From<Path<'a>> for PathBuf {
321+
fn from(path: Path<'a>) -> Self {
322+
match path {
323+
Path::Root => PathBuf::Root,
324+
Path::Keys(keys) => PathBuf::Keys(keys.into_iter().map(KeyBuf::from).collect()),
325+
}
326+
}
327+
}
288328
#[derive(Debug, Copy, Clone, PartialEq)]
289329
enum Key<'a> {
290330
Idx(usize),
291331
Field(&'a str),
292332
}
293333

334+
#[derive(Debug, Clone, PartialEq)]
335+
enum KeyBuf {
336+
Idx(usize),
337+
Field(String),
338+
}
339+
340+
impl<'a> From<Key<'a>> for KeyBuf {
341+
fn from(key: Key<'a>) -> Self {
342+
match key {
343+
Key::Idx(idx) => KeyBuf::Idx(idx),
344+
Key::Field(field) => KeyBuf::Field(field.to_owned()),
345+
}
346+
}
347+
}
348+
294349
impl<'a> fmt::Display for Key<'a> {
295350
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
296351
match self {

src/lib.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@
155155
use diff::diff;
156156
use serde::Serialize;
157157

158+
use crate::diff::DifferenceBuf;
159+
158160
mod core_ext;
159161
mod diff;
160162

@@ -251,18 +253,59 @@ macro_rules! assert_json_eq {
251253
#[macro_export]
252254
macro_rules! assert_json_matches {
253255
($lhs:expr, $rhs:expr, $config:expr $(,)?) => {{
254-
if let Err(error) = $crate::assert_json_matches_no_panic(&$lhs, &$rhs, $config) {
256+
if let Err(error) = $crate::assert_json_matches_no_panic_to_string(&$lhs, &$rhs, $config) {
255257
panic!("\n\n{}\n\n", error);
256258
}
257259
}};
258260
}
259261

262+
/// Compares two JSON values without panicking.
263+
///
264+
/// Returns a `Result` containing either `Ok(())` if the values match,
265+
/// or an `Err` with a `Vec<DifferenceBuf>` describing the differences.
266+
///
267+
/// # Note:
268+
///
269+
/// This function performs some cloning and may be less efficient.
270+
///
271+
/// If you only need a string error message, use [`assert_json_matches_no_panic_to_string`] or the assertion macros.
272+
pub fn assert_json_matches_no_panic<Lhs, Rhs>(
273+
lhs: &Lhs,
274+
rhs: &Rhs,
275+
config: Config,
276+
) -> Result<(), Vec<DifferenceBuf>>
277+
where
278+
Lhs: Serialize,
279+
Rhs: Serialize,
280+
{
281+
let lhs = serde_json::to_value(lhs).unwrap_or_else(|err| {
282+
panic!(
283+
"Couldn't convert left hand side value to JSON. Serde error: {}",
284+
err
285+
)
286+
});
287+
let rhs = serde_json::to_value(rhs).unwrap_or_else(|err| {
288+
panic!(
289+
"Couldn't convert right hand side value to JSON. Serde error: {}",
290+
err
291+
)
292+
});
293+
294+
let diffs = diff(&lhs, &rhs, config);
295+
let diffs: Vec<DifferenceBuf> = diffs.into_iter().map(|d| d.into()).collect();
296+
297+
if diffs.is_empty() {
298+
Ok(())
299+
} else {
300+
Err(diffs)
301+
}
302+
}
260303
/// Compares two JSON values without panicking.
261304
///
262305
/// Instead it returns a `Result` where the error is the message that would be passed to `panic!`.
263306
/// This is might be useful if you want to control how failures are reported and don't want to deal
264307
/// with panics.
265-
pub fn assert_json_matches_no_panic<Lhs, Rhs>(
308+
pub fn assert_json_matches_no_panic_to_string<Lhs, Rhs>(
266309
lhs: &Lhs,
267310
rhs: &Rhs,
268311
config: Config,
@@ -651,10 +694,10 @@ mod tests {
651694
}
652695

653696
fn test_partial_match(lhs: Value, rhs: Value) -> Result<(), String> {
654-
assert_json_matches_no_panic(&lhs, &rhs, Config::new(CompareMode::Inclusive))
697+
assert_json_matches_no_panic_to_string(&lhs, &rhs, Config::new(CompareMode::Inclusive))
655698
}
656699

657700
fn test_exact_match(lhs: Value, rhs: Value) -> Result<(), String> {
658-
assert_json_matches_no_panic(&lhs, &rhs, Config::new(CompareMode::Strict))
701+
assert_json_matches_no_panic_to_string(&lhs, &rhs, Config::new(CompareMode::Strict))
659702
}
660703
}

tests/integration_test.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use assert_json_diff::{
2-
assert_json_eq, assert_json_include, assert_json_matches, assert_json_matches_no_panic,
3-
CompareMode, Config, NumericMode,
1+
use json_diff::{
2+
assert_json_eq, assert_json_include, assert_json_matches,
3+
assert_json_matches_no_panic_to_string, CompareMode, Config, NumericMode,
44
};
55
use serde::Serialize;
66
use serde_json::json;
@@ -79,14 +79,14 @@ fn can_fail_with_exact_match() {
7979

8080
#[test]
8181
fn inclusive_match_without_panicking() {
82-
assert!(assert_json_matches_no_panic(
82+
assert!(assert_json_matches_no_panic_to_string(
8383
&json!({ "a": 1, "b": 2 }),
8484
&json!({ "b": 2}),
8585
Config::new(CompareMode::Inclusive,).numeric_mode(NumericMode::Strict),
8686
)
8787
.is_ok());
8888

89-
assert!(assert_json_matches_no_panic(
89+
assert!(assert_json_matches_no_panic_to_string(
9090
&json!({ "a": 1, "b": 2 }),
9191
&json!("foo"),
9292
Config::new(CompareMode::Inclusive,).numeric_mode(NumericMode::Strict),
@@ -96,14 +96,14 @@ fn inclusive_match_without_panicking() {
9696

9797
#[test]
9898
fn exact_match_without_panicking() {
99-
assert!(assert_json_matches_no_panic(
99+
assert!(assert_json_matches_no_panic_to_string(
100100
&json!([1, 2, 3]),
101101
&json!([1, 2, 3]),
102102
Config::new(CompareMode::Strict).numeric_mode(NumericMode::Strict)
103103
)
104104
.is_ok());
105105

106-
assert!(assert_json_matches_no_panic(
106+
assert!(assert_json_matches_no_panic_to_string(
107107
&json!([1, 2, 3]),
108108
&json!("foo"),
109109
Config::new(CompareMode::Strict).numeric_mode(NumericMode::Strict)

0 commit comments

Comments
 (0)