1#![deny(
165 missing_docs,
166 unused_imports,
167 missing_debug_implementations,
168 missing_copy_implementations,
169 trivial_casts,
170 trivial_numeric_casts,
171 unsafe_code,
172 unstable_features,
173 unused_import_braces,
174 unused_qualifications,
175 unknown_lints
176)]
177
178use diff::diff;
179use serde::Serialize;
180
181pub use crate::diff::{Difference, Key, Path};
182
183mod core_ext;
184mod diff;
185
186#[macro_export]
190macro_rules! assert_json_contains {
191 (container: $container:expr, contained: $contained:expr $(,)?) => {{
192 let config =
193 $crate::Config::new($crate::CompareMode::Inclusive).consider_array_sorting(false);
194 $crate::assert_json_matches!($container, $contained, &config)
195 }};
196 (container: $container:expr, contained: $contained:expr, $($arg:tt)+) => {{
197 let config =
198 $crate::Config::new($crate::CompareMode::Inclusive).consider_array_sorting(false);
199 $crate::assert_json_matches!($container, $contained, &config, $($arg)+)
200 }};
201}
202
203#[macro_export]
210macro_rules! assert_json_include {
211 (actual: $actual:expr, expected: $expected:expr $(,)?) => {{
212 let config = $crate::Config::new($crate::CompareMode::Inclusive);
213 $crate::assert_json_matches!($actual, $expected, &config)
214 }};
215 (expected: $expected:expr, actual: $actual:expr $(,)?) => {{
216 $crate::assert_json_include!(actual: $actual, expected: $expected)
217 }};
218 (actual: $actual:expr, expected: $expected:expr, $($arg:tt)+) => {{
219 let config = $crate::Config::new($crate::CompareMode::Inclusive);
220 $crate::assert_json_matches!($actual, $expected, &config, $($arg)+)
221 }};
222 (expected: $expected:expr, actual: $actual:expr, $($arg:tt)+) => {{
223 $crate::assert_json_include!(actual: $actual, expected: $expected, $($arg)+)
224 }};
225}
226
227#[macro_export]
234macro_rules! assert_json_eq {
235 ($lhs:expr, $rhs:expr $(,)?) => {{
236 let config = $crate::Config::new($crate::CompareMode::Strict);
237 $crate::assert_json_matches!($lhs, $rhs, &config)
238 }};
239 ($lhs:expr, $rhs:expr, $($arg:tt)+) => {{
240 let config = $crate::Config::new($crate::CompareMode::Strict);
241 $crate::assert_json_matches!($lhs, $rhs, &config, $($arg)+)
242 }};
243}
244
245#[macro_export]
318macro_rules! assert_json_matches {
319 ($lhs:expr, $rhs:expr, $config:expr $(,)?) => {{
320 if let Err(error) = $crate::assert_json_matches_no_panic(&$lhs, &$rhs, $config) {
321 panic!("\n{}", error);
322 }
323 }};
324 ($lhs:expr, $rhs:expr, $config:expr, $($arg:tt)+) => {{
325 if let Err(error) = $crate::assert_json_matches_no_panic(&$lhs, &$rhs, $config) {
326 panic!("\n{}\n\n{}", format_args!($($arg)+), error);
327 }
328 }};
329}
330
331pub fn assert_json_matches_no_panic<Lhs, Rhs>(
337 lhs: &Lhs,
338 rhs: &Rhs,
339 config: &Config,
340) -> Result<(), String>
341where
342 Lhs: Serialize,
343 Rhs: Serialize,
344{
345 let lhs = serde_json::to_value(lhs).unwrap_or_else(|err| {
346 panic!(
347 "Couldn't convert left hand side value to JSON. Serde error: {}",
348 err
349 )
350 });
351 let rhs = serde_json::to_value(rhs).unwrap_or_else(|err| {
352 panic!(
353 "Couldn't convert right hand side value to JSON. Serde error: {}",
354 err
355 )
356 });
357
358 let diffs = diff(&lhs, &rhs, config);
359
360 if diffs.is_empty() {
361 Ok(())
362 } else {
363 let msg = diffs
364 .into_iter()
365 .map(|d| d.to_string())
366 .collect::<Vec<_>>()
367 .join("\n\n");
368 Err(msg)
369 }
370}
371
372pub fn try_assert_json_matches<Lhs, Rhs>(
405 lhs: &Lhs,
406 rhs: &Rhs,
407 config: &Config,
408) -> Result<(), Vec<Difference>>
409where
410 Lhs: Serialize,
411 Rhs: Serialize,
412{
413 let lhs = serde_json::to_value(lhs).unwrap_or_else(|err| {
414 panic!(
415 "Couldn't convert left hand side value to JSON. Serde error: {}",
416 err
417 )
418 });
419 let rhs = serde_json::to_value(rhs).unwrap_or_else(|err| {
420 panic!(
421 "Couldn't convert right hand side value to JSON. Serde error: {}",
422 err
423 )
424 });
425
426 let diffs = diff(&lhs, &rhs, config);
427 let diffs_buf: Vec<Difference> = diffs.into_iter().map(|d| d.into()).collect();
428
429 if diffs_buf.is_empty() {
430 Ok(())
431 } else {
432 Err(diffs_buf)
433 }
434}
435
436#[derive(Debug, Clone, PartialEq)]
438#[allow(missing_copy_implementations)]
439pub struct Config {
440 pub(crate) array_sorting_mode: ArraySortingMode,
441 pub(crate) compare_mode: CompareMode,
442 pub(crate) numeric_mode: NumericMode,
443 float_compare_mode: FloatCompareMode,
444}
445
446impl Config {
447 pub fn new(compare_mode: CompareMode) -> Self {
451 Self {
452 array_sorting_mode: ArraySortingMode::Consider,
453 compare_mode,
454 numeric_mode: NumericMode::Strict,
455 float_compare_mode: FloatCompareMode::Exact,
456 }
457 }
458
459 pub fn numeric_mode(mut self, numeric_mode: NumericMode) -> Self {
463 self.numeric_mode = numeric_mode;
464 self
465 }
466
467 pub fn compare_mode(mut self, compare_mode: CompareMode) -> Self {
469 self.compare_mode = compare_mode;
470 self
471 }
472
473 pub fn float_compare_mode(mut self, float_compare_mode: FloatCompareMode) -> Self {
477 self.float_compare_mode = float_compare_mode;
478 self
479 }
480
481 pub fn consider_array_sorting(mut self, consider: bool) -> Self {
483 if consider {
484 if self.compare_mode == CompareMode::Strict {
485 panic!("strict comparison does not allow array ordering to be ignored");
486 }
487 self.array_sorting_mode = ArraySortingMode::Consider;
488 } else {
489 self.array_sorting_mode = ArraySortingMode::Ignore;
490 }
491 self
492 }
493}
494
495#[derive(Debug, Copy, Clone, PartialEq, Eq)]
497pub enum CompareMode {
498 Inclusive,
503 Strict,
507}
508
509#[derive(Debug, Copy, Clone, PartialEq, Eq)]
511enum ArraySortingMode {
512 Consider,
514 Ignore,
516}
517
518#[derive(Debug, Copy, Clone, PartialEq, Eq)]
520pub enum NumericMode {
521 Strict,
523 AssumeFloat,
525}
526
527#[derive(Debug, Copy, Clone, PartialEq)]
529pub enum FloatCompareMode {
530 Exact,
532 Epsilon(f64),
534}
535
536impl Eq for FloatCompareMode {}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541 use serde_json::{json, Value};
542 use std::fmt::Write;
543
544 #[test]
545 fn boolean_root() {
546 let result = test_partial_match(json!(true), json!(true));
547 assert_output_eq(result, Ok(()));
548
549 let result = test_partial_match(json!(false), json!(false));
550 assert_output_eq(result, Ok(()));
551
552 let result = test_partial_match(json!(false), json!(true));
553 assert_output_eq(
554 result,
555 Err(r#"json atoms at path "(root)" are not equal:
556 expected:
557 true
558 actual:
559 false"#),
560 );
561
562 let result = test_partial_match(json!(true), json!(false));
563 assert_output_eq(
564 result,
565 Err(r#"json atoms at path "(root)" are not equal:
566 expected:
567 false
568 actual:
569 true"#),
570 );
571 }
572
573 #[test]
574 fn string_root() {
575 let result = test_partial_match(json!("true"), json!("true"));
576 assert_output_eq(result, Ok(()));
577
578 let result = test_partial_match(json!("false"), json!("false"));
579 assert_output_eq(result, Ok(()));
580
581 let result = test_partial_match(json!("false"), json!("true"));
582 assert_output_eq(
583 result,
584 Err(r#"json atoms at path "(root)" are not equal:
585 expected:
586 "true"
587 actual:
588 "false""#),
589 );
590
591 let result = test_partial_match(json!("true"), json!("false"));
592 assert_output_eq(
593 result,
594 Err(r#"json atoms at path "(root)" are not equal:
595 expected:
596 "false"
597 actual:
598 "true""#),
599 );
600 }
601
602 #[test]
603 fn number_root() {
604 let result = test_partial_match(json!(1), json!(1));
605 assert_output_eq(result, Ok(()));
606
607 let result = test_partial_match(json!(0), json!(0));
608 assert_output_eq(result, Ok(()));
609
610 let result = test_partial_match(json!(0), json!(1));
611 assert_output_eq(
612 result,
613 Err(r#"json atoms at path "(root)" are not equal:
614 expected:
615 1
616 actual:
617 0"#),
618 );
619
620 let result = test_partial_match(json!(1), json!(0));
621 assert_output_eq(
622 result,
623 Err(r#"json atoms at path "(root)" are not equal:
624 expected:
625 0
626 actual:
627 1"#),
628 );
629 }
630
631 #[test]
632 fn null_root() {
633 let result = test_partial_match(json!(null), json!(null));
634 assert_output_eq(result, Ok(()));
635
636 let result = test_partial_match(json!(null), json!(1));
637 assert_output_eq(
638 result,
639 Err(r#"json atoms at path "(root)" are not equal:
640 expected:
641 1
642 actual:
643 null"#),
644 );
645
646 let result = test_partial_match(json!(1), json!(null));
647 assert_output_eq(
648 result,
649 Err(r#"json atoms at path "(root)" are not equal:
650 expected:
651 null
652 actual:
653 1"#),
654 );
655 }
656
657 #[test]
658 fn into_object() {
659 let result = test_partial_match(json!({ "a": true }), json!({ "a": true }));
660 assert_output_eq(result, Ok(()));
661
662 let result = test_partial_match(json!({ "a": false }), json!({ "a": true }));
663 assert_output_eq(
664 result,
665 Err(r#"json atoms at path ".a" are not equal:
666 expected:
667 true
668 actual:
669 false"#),
670 );
671
672 let result =
673 test_partial_match(json!({ "a": { "b": true } }), json!({ "a": { "b": true } }));
674 assert_output_eq(result, Ok(()));
675
676 let result = test_partial_match(json!({ "a": true }), json!({ "a": { "b": true } }));
677 assert_output_eq(
678 result,
679 Err(r#"json atoms at path ".a" are not equal:
680 expected:
681 {
682 "b": true
683 }
684 actual:
685 true"#),
686 );
687
688 let result = test_partial_match(json!({}), json!({ "a": true }));
689 assert_output_eq(
690 result,
691 Err(r#"json atom at path ".a" is missing from actual"#),
692 );
693
694 let result = test_partial_match(json!({ "a": { "b": true } }), json!({ "a": true }));
695 assert_output_eq(
696 result,
697 Err(r#"json atoms at path ".a" are not equal:
698 expected:
699 true
700 actual:
701 {
702 "b": true
703 }"#),
704 );
705 }
706
707 #[test]
708 fn into_array() {
709 let result = test_partial_match(json!([1]), json!([1]));
710 assert_output_eq(result, Ok(()));
711
712 let result = test_partial_match(json!([2]), json!([1]));
713 assert_output_eq(
714 result,
715 Err(r#"json atoms at path "[0]" are not equal:
716 expected:
717 1
718 actual:
719 2"#),
720 );
721
722 let result = test_partial_match(json!([1, 2, 4]), json!([1, 2, 3]));
723 assert_output_eq(
724 result,
725 Err(r#"json atoms at path "[2]" are not equal:
726 expected:
727 3
728 actual:
729 4"#),
730 );
731
732 let result = test_partial_match(json!({ "a": [1, 2, 3]}), json!({ "a": [1, 2, 4]}));
733 assert_output_eq(
734 result,
735 Err(r#"json atoms at path ".a[2]" are not equal:
736 expected:
737 4
738 actual:
739 3"#),
740 );
741
742 let result = test_partial_match(json!({ "a": [1, 2, 3]}), json!({ "a": [1, 2]}));
743 assert_output_eq(result, Ok(()));
744
745 let result = test_partial_match(json!({ "a": [1, 2]}), json!({ "a": [1, 2, 3]}));
746 assert_output_eq(
747 result,
748 Err(r#"json atom at path ".a[2]" is missing from actual"#),
749 );
750 }
751
752 #[test]
753 fn exact_matching() {
754 let result = test_exact_match(json!(true), json!(true));
755 assert_output_eq(result, Ok(()));
756
757 let result = test_exact_match(json!("s"), json!("s"));
758 assert_output_eq(result, Ok(()));
759
760 let result = test_exact_match(json!("a"), json!("b"));
761 assert_output_eq(
762 result,
763 Err(r#"json atoms at path "(root)" are not equal:
764 lhs:
765 "a"
766 rhs:
767 "b""#),
768 );
769
770 let result = test_exact_match(
771 json!({ "a": [1, { "b": 2 }] }),
772 json!({ "a": [1, { "b": 3 }] }),
773 );
774 assert_output_eq(
775 result,
776 Err(r#"json atoms at path ".a[1].b" are not equal:
777 lhs:
778 2
779 rhs:
780 3"#),
781 );
782 }
783
784 #[test]
785 fn exact_match_output_message() {
786 let result = test_exact_match(json!({ "a": { "b": 1 } }), json!({ "a": {} }));
787 assert_output_eq(
788 result,
789 Err(r#"json atom at path ".a.b" is missing from rhs"#),
790 );
791
792 let result = test_exact_match(json!({ "a": {} }), json!({ "a": { "b": 1 } }));
793 assert_output_eq(
794 result,
795 Err(r#"json atom at path ".a.b" is missing from lhs"#),
796 );
797 }
798
799 fn assert_output_eq(actual: Result<(), String>, expected: Result<(), &str>) {
800 match (actual, expected) {
801 (Ok(()), Ok(())) => {}
802
803 (Err(actual_error), Ok(())) => {
804 let mut f = String::new();
805 writeln!(f, "Did not expect error, but got").unwrap();
806 writeln!(f, "{}", actual_error).unwrap();
807 panic!("{}", f);
808 }
809
810 (Ok(()), Err(expected_error)) => {
811 let expected_error = expected_error.to_string();
812 let mut f = String::new();
813 writeln!(f, "Expected error, but did not get one. Expected error:").unwrap();
814 writeln!(f, "{}", expected_error).unwrap();
815 panic!("{}", f);
816 }
817
818 (Err(actual_error), Err(expected_error)) => {
819 let expected_error = expected_error.to_string();
820 if actual_error != expected_error {
821 let mut f = String::new();
822 writeln!(f, "Errors didn't match").unwrap();
823 writeln!(f, "Expected:").unwrap();
824 writeln!(f, "{}", expected_error).unwrap();
825 writeln!(f, "Got:").unwrap();
826 writeln!(f, "{}", actual_error).unwrap();
827 panic!("{}", f);
828 }
829 }
830 }
831 }
832
833 fn test_partial_match(lhs: Value, rhs: Value) -> Result<(), String> {
834 assert_json_matches_no_panic(&lhs, &rhs, &Config::new(CompareMode::Inclusive))
835 }
836
837 fn test_exact_match(lhs: Value, rhs: Value) -> Result<(), String> {
838 assert_json_matches_no_panic(&lhs, &rhs, &Config::new(CompareMode::Strict))
839 }
840}