googletest_json_serde/matchers/
elements_are_matcher.rs

1/// Matches a JSON array against a list of matchers in order.
2///
3/// The array length must equal the matcher count.
4///
5/// # Examples
6///
7/// ```rust
8/// # use googletest::prelude::*;
9/// # use serde_json::json as j;
10/// # use crate::googletest_json_serde::json;
11/// let value = j!(["alex", "bart", "cucumberbatch"]);
12/// assert_that!(
13///     value,
14///     json::elements_are![
15///         "alex",
16///         j!("bart"),
17///         char_count(eq(13))
18///     ]
19/// );
20/// ```
21///
22/// ```rust,should_panic
23/// # use googletest::prelude::*;
24/// # use serde_json::json as j;
25/// # use crate::googletest_json_serde::json;
26/// let value = j!(["cucumberbatch", "alex", "bart"]);
27/// assert_that!(
28///     value,
29///     json::elements_are![
30///         "alex",
31///         j!("bart"),
32///         char_count(eq(13))
33///     ]
34/// );
35/// ```
36///
37/// # Errors
38///
39/// Fails when the value is not a JSON array or when lengths differ.
40///
41/// # Supported Inputs
42/// - Literal JSON-compatible values
43/// - Direct `serde_json::Value`
44/// - Native googletest matchers
45#[macro_export]
46#[doc(hidden)]
47macro_rules! __json_elements_are {
48    // Preferred bracketed form: __json_elements_are!([ m1, m2, ... ])
49    ([$($matcher:expr),* $(,)?]) => {{
50        $crate::matchers::__internal_unstable_do_not_depend_on_these::JsonElementsAre::new(vec![
51            $(
52                $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($matcher)
53            ),*
54        ])
55    }};
56    // Convenience: allow unbracketed list and forward to the bracketed arm.
57    ($($matcher:expr),* $(,)?) => {{
58        $crate::__json_elements_are!([$($matcher),*])
59    }};
60}
61
62#[doc(hidden)]
63pub mod internal {
64    use crate::matchers::json_matcher::internal::JsonMatcher;
65    use googletest::description::Description;
66    use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
67    use serde_json::Value;
68
69    #[doc(hidden)]
70    #[derive(MatcherBase)]
71    pub struct JsonElementsAre {
72        elements: Vec<Box<dyn JsonMatcher>>,
73    }
74
75    impl JsonMatcher for JsonElementsAre {}
76
77    impl JsonElementsAre {
78        pub fn new(elements: Vec<Box<dyn JsonMatcher>>) -> Self {
79            Self { elements }
80        }
81    }
82
83    impl Matcher<&Value> for JsonElementsAre {
84        fn matches(&self, actual: &Value) -> MatcherResult {
85            match actual {
86                Value::Array(arr) => {
87                    if arr.len() != self.elements.len() {
88                        return MatcherResult::NoMatch;
89                    }
90                    for (item, matcher) in arr.iter().zip(&self.elements) {
91                        if matcher.matches(item).is_no_match() {
92                            return MatcherResult::NoMatch;
93                        }
94                    }
95                    MatcherResult::Match
96                }
97                _ => MatcherResult::NoMatch,
98            }
99        }
100
101        fn describe(&self, result: MatcherResult) -> Description {
102            let verb = if result.into() { "has" } else { "doesn't have" };
103            let inner = self
104                .elements
105                .iter()
106                .map(|m| m.describe(MatcherResult::Match))
107                .collect::<Description>()
108                .enumerate()
109                .indent();
110
111            format!("{verb} JSON array elements:\n{inner}").into()
112        }
113
114        fn explain_match(&self, actual: &Value) -> Description {
115            match actual {
116                Value::Array(arr) => {
117                    let mut mismatches = Vec::new();
118                    let actual_len = arr.len();
119                    let expected_len = self.elements.len();
120
121                    for (index, (item, matcher)) in arr.iter().zip(&self.elements).enumerate() {
122                        if matcher.matches(item).is_no_match() {
123                            mismatches.push(format!(
124                                "element #{index} is {item:?}, {}",
125                                matcher.explain_match(item)
126                            ));
127                        }
128                    }
129
130                    if mismatches.is_empty() {
131                        if actual_len == expected_len {
132                            "whose elements all match".into()
133                        } else {
134                            format!("whose size is {}", actual_len).into()
135                        }
136                    } else if mismatches.len() == 1 {
137                        let description = mismatches.into_iter().collect::<Description>();
138                        format!("where {description}").into()
139                    } else {
140                        let description = mismatches.into_iter().collect::<Description>();
141                        format!("where:\n{}", description.bullet_list().indent()).into()
142                    }
143                }
144                _ => Description::new().text("where the type is not array".to_string()),
145            }
146        }
147    }
148}