1#![deny(
142 missing_docs,
143 unused_imports,
144 missing_debug_implementations,
145 missing_copy_implementations,
146 trivial_casts,
147 trivial_numeric_casts,
148 unsafe_code,
149 unstable_features,
150 unused_import_braces,
151 unused_qualifications,
152 unknown_lints
153)]
154
155use diff::diff;
156use serde::Serialize;
157
158use crate::diff::DifferenceBuf;
159
160mod core_ext;
161mod diff;
162
163#[macro_export]
170macro_rules! assert_json_include {
171 (actual: $actual:expr, expected: $expected:expr $(,)?) => {{
172 $crate::assert_json_matches!(
173 $actual,
174 $expected,
175 $crate::Config::new($crate::CompareMode::Inclusive)
176 )
177 }};
178 (expected: $expected:expr, actual: $actual:expr $(,)?) => {{
179 $crate::assert_json_include!(actual: $actual, expected: $expected)
180 }};
181}
182
183#[macro_export]
189macro_rules! assert_json_eq {
190 ($lhs:expr, $rhs:expr $(,)?) => {{
191 $crate::assert_json_matches!($lhs, $rhs, $crate::Config::new($crate::CompareMode::Strict))
192 }};
193}
194
195#[macro_export]
254macro_rules! assert_json_matches {
255 ($lhs:expr, $rhs:expr, $config:expr $(,)?) => {{
256 if let Err(error) = $crate::assert_json_matches_no_panic_to_string(&$lhs, &$rhs, $config) {
257 panic!("\n\n{}\n\n", error);
258 }
259 }};
260}
261
262pub fn assert_json_matches_no_panic<Lhs, Rhs>(
273 lhs: &Lhs,
274 rhs: &Rhs,
275 config: Config,
276) -> Result<(), Vec<DifferenceBuf>>
277where
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}
303pub fn assert_json_matches_no_panic_to_string<Lhs, Rhs>(
309 lhs: &Lhs,
310 rhs: &Rhs,
311 config: Config,
312) -> Result<(), String>
313where
314 Lhs: Serialize,
315 Rhs: Serialize,
316{
317 let lhs = serde_json::to_value(lhs).unwrap_or_else(|err| {
318 panic!(
319 "Couldn't convert left hand side value to JSON. Serde error: {}",
320 err
321 )
322 });
323 let rhs = serde_json::to_value(rhs).unwrap_or_else(|err| {
324 panic!(
325 "Couldn't convert right hand side value to JSON. Serde error: {}",
326 err
327 )
328 });
329
330 let diffs = diff(&lhs, &rhs, config);
331
332 if diffs.is_empty() {
333 Ok(())
334 } else {
335 let msg = diffs
336 .into_iter()
337 .map(|d| d.to_string())
338 .collect::<Vec<_>>()
339 .join("\n\n");
340 Err(msg)
341 }
342}
343
344#[derive(Debug, Clone, Copy, PartialEq)]
346#[allow(missing_copy_implementations)]
347pub struct Config {
348 pub(crate) compare_mode: CompareMode,
349 pub(crate) numeric_mode: NumericMode,
350}
351
352impl Config {
353 pub fn new(compare_mode: CompareMode) -> Self {
357 Self {
358 compare_mode,
359 numeric_mode: NumericMode::Strict,
360 }
361 }
362
363 pub fn numeric_mode(mut self, numeric_mode: NumericMode) -> Self {
367 self.numeric_mode = numeric_mode;
368 self
369 }
370
371 pub fn compare_mode(mut self, compare_mode: CompareMode) -> Self {
373 self.compare_mode = compare_mode;
374 self
375 }
376}
377
378#[derive(Debug, Copy, Clone, PartialEq, Eq)]
380pub enum CompareMode {
381 Inclusive,
386 Strict,
390}
391
392#[derive(Debug, Copy, Clone, PartialEq)]
394pub enum NumericMode {
395 Strict,
397 AssumeFloat,
399
400 AssumeFloatEpsilon(f64),
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407 use serde_json::{json, Value};
408 use std::fmt::Write;
409
410 #[test]
411 fn boolean_root() {
412 let result = test_partial_match(json!(true), json!(true));
413 assert_output_eq(result, Ok(()));
414
415 let result = test_partial_match(json!(false), json!(false));
416 assert_output_eq(result, Ok(()));
417
418 let result = test_partial_match(json!(false), json!(true));
419 assert_output_eq(
420 result,
421 Err(r#"json atoms at path "(root)" are not equal:
422 expected:
423 true
424 actual:
425 false"#),
426 );
427
428 let result = test_partial_match(json!(true), json!(false));
429 assert_output_eq(
430 result,
431 Err(r#"json atoms at path "(root)" are not equal:
432 expected:
433 false
434 actual:
435 true"#),
436 );
437 }
438
439 #[test]
440 fn string_root() {
441 let result = test_partial_match(json!("true"), json!("true"));
442 assert_output_eq(result, Ok(()));
443
444 let result = test_partial_match(json!("false"), json!("false"));
445 assert_output_eq(result, Ok(()));
446
447 let result = test_partial_match(json!("false"), json!("true"));
448 assert_output_eq(
449 result,
450 Err(r#"json atoms at path "(root)" are not equal:
451 expected:
452 "true"
453 actual:
454 "false""#),
455 );
456
457 let result = test_partial_match(json!("true"), json!("false"));
458 assert_output_eq(
459 result,
460 Err(r#"json atoms at path "(root)" are not equal:
461 expected:
462 "false"
463 actual:
464 "true""#),
465 );
466 }
467
468 #[test]
469 fn number_root() {
470 let result = test_partial_match(json!(1), json!(1));
471 assert_output_eq(result, Ok(()));
472
473 let result = test_partial_match(json!(0), json!(0));
474 assert_output_eq(result, Ok(()));
475
476 let result = test_partial_match(json!(0), json!(1));
477 assert_output_eq(
478 result,
479 Err(r#"json atoms at path "(root)" are not equal:
480 expected:
481 1
482 actual:
483 0"#),
484 );
485
486 let result = test_partial_match(json!(1), json!(0));
487 assert_output_eq(
488 result,
489 Err(r#"json atoms at path "(root)" are not equal:
490 expected:
491 0
492 actual:
493 1"#),
494 );
495 }
496
497 #[test]
498 fn null_root() {
499 let result = test_partial_match(json!(null), json!(null));
500 assert_output_eq(result, Ok(()));
501
502 let result = test_partial_match(json!(null), json!(1));
503 assert_output_eq(
504 result,
505 Err(r#"json atoms at path "(root)" are not equal:
506 expected:
507 1
508 actual:
509 null"#),
510 );
511
512 let result = test_partial_match(json!(1), json!(null));
513 assert_output_eq(
514 result,
515 Err(r#"json atoms at path "(root)" are not equal:
516 expected:
517 null
518 actual:
519 1"#),
520 );
521 }
522
523 #[test]
524 fn into_object() {
525 let result = test_partial_match(json!({ "a": true }), json!({ "a": true }));
526 assert_output_eq(result, Ok(()));
527
528 let result = test_partial_match(json!({ "a": false }), json!({ "a": true }));
529 assert_output_eq(
530 result,
531 Err(r#"json atoms at path ".a" are not equal:
532 expected:
533 true
534 actual:
535 false"#),
536 );
537
538 let result =
539 test_partial_match(json!({ "a": { "b": true } }), json!({ "a": { "b": true } }));
540 assert_output_eq(result, Ok(()));
541
542 let result = test_partial_match(json!({ "a": true }), json!({ "a": { "b": true } }));
543 assert_output_eq(
544 result,
545 Err(r#"json atoms at path ".a" are not equal:
546 expected:
547 {
548 "b": true
549 }
550 actual:
551 true"#),
552 );
553
554 let result = test_partial_match(json!({}), json!({ "a": true }));
555 assert_output_eq(
556 result,
557 Err(r#"json atom at path ".a" is missing from actual"#),
558 );
559
560 let result = test_partial_match(json!({ "a": { "b": true } }), json!({ "a": true }));
561 assert_output_eq(
562 result,
563 Err(r#"json atoms at path ".a" are not equal:
564 expected:
565 true
566 actual:
567 {
568 "b": true
569 }"#),
570 );
571 }
572
573 #[test]
574 fn into_array() {
575 let result = test_partial_match(json!([1]), json!([1]));
576 assert_output_eq(result, Ok(()));
577
578 let result = test_partial_match(json!([2]), json!([1]));
579 assert_output_eq(
580 result,
581 Err(r#"json atoms at path "[0]" are not equal:
582 expected:
583 1
584 actual:
585 2"#),
586 );
587
588 let result = test_partial_match(json!([1, 2, 4]), json!([1, 2, 3]));
589 assert_output_eq(
590 result,
591 Err(r#"json atoms at path "[2]" are not equal:
592 expected:
593 3
594 actual:
595 4"#),
596 );
597
598 let result = test_partial_match(json!({ "a": [1, 2, 3]}), json!({ "a": [1, 2, 4]}));
599 assert_output_eq(
600 result,
601 Err(r#"json atoms at path ".a[2]" are not equal:
602 expected:
603 4
604 actual:
605 3"#),
606 );
607
608 let result = test_partial_match(json!({ "a": [1, 2, 3]}), json!({ "a": [1, 2]}));
609 assert_output_eq(result, Ok(()));
610
611 let result = test_partial_match(json!({ "a": [1, 2]}), json!({ "a": [1, 2, 3]}));
612 assert_output_eq(
613 result,
614 Err(r#"json atom at path ".a[2]" is missing from actual"#),
615 );
616 }
617
618 #[test]
619 fn exact_matching() {
620 let result = test_exact_match(json!(true), json!(true));
621 assert_output_eq(result, Ok(()));
622
623 let result = test_exact_match(json!("s"), json!("s"));
624 assert_output_eq(result, Ok(()));
625
626 let result = test_exact_match(json!("a"), json!("b"));
627 assert_output_eq(
628 result,
629 Err(r#"json atoms at path "(root)" are not equal:
630 lhs:
631 "a"
632 rhs:
633 "b""#),
634 );
635
636 let result = test_exact_match(
637 json!({ "a": [1, { "b": 2 }] }),
638 json!({ "a": [1, { "b": 3 }] }),
639 );
640 assert_output_eq(
641 result,
642 Err(r#"json atoms at path ".a[1].b" are not equal:
643 lhs:
644 2
645 rhs:
646 3"#),
647 );
648 }
649
650 #[test]
651 fn exact_match_output_message() {
652 let result = test_exact_match(json!({ "a": { "b": 1 } }), json!({ "a": {} }));
653 assert_output_eq(
654 result,
655 Err(r#"json atom at path ".a.b" is missing from rhs"#),
656 );
657
658 let result = test_exact_match(json!({ "a": {} }), json!({ "a": { "b": 1 } }));
659 assert_output_eq(
660 result,
661 Err(r#"json atom at path ".a.b" is missing from lhs"#),
662 );
663 }
664
665 fn assert_output_eq(actual: Result<(), String>, expected: Result<(), &str>) {
666 match (actual, expected) {
667 (Ok(()), Ok(())) => {}
668
669 (Err(actual_error), Ok(())) => {
670 let mut f = String::new();
671 writeln!(f, "Did not expect error, but got").unwrap();
672 writeln!(f, "{}", actual_error).unwrap();
673 panic!("{}", f);
674 }
675
676 (Ok(()), Err(expected_error)) => {
677 let expected_error = expected_error.to_string();
678 let mut f = String::new();
679 writeln!(f, "Expected error, but did not get one. Expected error:").unwrap();
680 writeln!(f, "{}", expected_error).unwrap();
681 panic!("{}", f);
682 }
683
684 (Err(actual_error), Err(expected_error)) => {
685 let expected_error = expected_error.to_string();
686 if actual_error != expected_error {
687 let mut f = String::new();
688 writeln!(f, "Errors didn't match").unwrap();
689 writeln!(f, "Expected:").unwrap();
690 writeln!(f, "{}", expected_error).unwrap();
691 writeln!(f, "Got:").unwrap();
692 writeln!(f, "{}", actual_error).unwrap();
693 panic!("{}", f);
694 }
695 }
696 }
697 }
698
699 fn test_partial_match(lhs: Value, rhs: Value) -> Result<(), String> {
700 assert_json_matches_no_panic_to_string(&lhs, &rhs, Config::new(CompareMode::Inclusive))
701 }
702
703 fn test_exact_match(lhs: Value, rhs: Value) -> Result<(), String> {
704 assert_json_matches_no_panic_to_string(&lhs, &rhs, Config::new(CompareMode::Strict))
705 }
706}