googletest_json_serde/matchers/
each_matcher.rs

1/// Matches every element of a JSON array against a single matcher.
2///
3/// This allows writing expressive assertions such as:
4///
5/// ```rust
6/// use googletest::prelude::*;
7/// use googletest_json_serde::json;
8/// use serde_json::json as j;
9///
10/// assert_that!(j!([1, 2, 3]), json::each!(gt(0)));
11/// assert_that!(j!(["ab", "ax"]), json::each!(starts_with("a")));
12/// ```
13///
14/// Fails if:
15/// - the value is not a JSON array
16/// - any element fails the provided matcher
17///
18/// This behaves similarly to `each()` in googletest‑rust, but specialized for `serde_json::Value`.
19#[macro_export]
20macro_rules! __json_each {
21    ($inner:expr) => {
22        $crate::matchers::__internal_unstable_do_not_depend_on_these::JsonEachMatcher::new(
23            $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($inner)
24        )
25    };
26}
27
28pub mod internal {
29    use crate::matchers::__internal_unstable_do_not_depend_on_these::JsonMatcher;
30    use googletest::description::Description;
31    use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
32    use serde_json::Value;
33
34    #[derive(MatcherBase)]
35    pub struct JsonEachMatcher {
36        inner: Box<dyn JsonMatcher>,
37    }
38
39    impl JsonEachMatcher {
40        pub fn new(inner: Box<dyn JsonMatcher>) -> Self {
41            Self { inner }
42        }
43    }
44
45    impl JsonMatcher for JsonEachMatcher {}
46    impl Matcher<&Value> for JsonEachMatcher {
47        fn matches(&self, actual: &Value) -> MatcherResult {
48            let arr = match actual {
49                Value::Array(a) => a,
50                _ => return MatcherResult::NoMatch,
51            };
52            for v in arr {
53                if self.inner.matches(v) == MatcherResult::NoMatch {
54                    return MatcherResult::NoMatch;
55                }
56            }
57            MatcherResult::Match
58        }
59
60        fn describe(&self, result: MatcherResult) -> Description {
61            match result {
62                MatcherResult::Match => format!(
63                    "JSON array where each element {}",
64                    self.inner.describe(MatcherResult::Match)
65                )
66                .into(),
67                MatcherResult::NoMatch => format!(
68                    "JSON array where each element {}",
69                    self.inner.describe(MatcherResult::NoMatch)
70                )
71                .into(),
72            }
73        }
74
75        fn explain_match(&self, actual: &Value) -> Description {
76            let arr = match actual {
77                Value::Array(a) => a,
78                _ => return Description::new().text("which is not a JSON array"),
79            };
80            for (i, v) in arr.iter().enumerate() {
81                if self.inner.matches(v) == MatcherResult::NoMatch {
82                    return format!(
83                        "element #{} ({}) did not match: {}",
84                        i,
85                        v,
86                        self.inner.explain_match(v)
87                    )
88                    .into();
89                }
90            }
91            format!(
92                "all {} elements matched: {}",
93                arr.len(),
94                self.inner.describe(MatcherResult::Match)
95            )
96            .into()
97        }
98    }
99}