googletest_json_serde/matchers/
unordered_elements_are_matcher.rs

1//! JSON matcher for arrays where element order does **not** matter.
2//!
3//! This mirrors the semantics of `googletest::matchers::unordered_elements_are`,
4//! but works for `&serde_json::Value` arrays and delegates each element to
5//! inner matchers you provide.
6
7/// Matches a JSON array whose elements, in any order, have a 1:1 correspondence
8/// with the provided matchers.
9///
10/// Each element in the input array must match exactly one of the given matchers,
11/// and vice versa. Matching fails if the input is not an array, if the number of
12/// elements and matchers differ, or if no perfect one-to-one mapping can be found.
13///
14/// # Examples
15///
16/// This passes:
17/// ```
18/// # use googletest::prelude::*;
19/// # use serde_json::json;
20/// # use crate::googletest_json_serde::json;
21/// let value = json!(["a", "b", "c"]);
22/// assert_that!(
23///     value,
24///     json::unordered_elements_are![
25///         eq("c"),
26///         eq("a"),
27///         starts_with("b"),
28///     ]
29/// );
30/// ```
31///
32/// This fails because the element `"x"` does not match any expected element:
33/// ```should_panic
34/// # use googletest::prelude::*;
35/// # use serde_json::json;
36/// # use crate::googletest_json_serde::json;
37/// let value = json!(["a", "x", "c"]);
38/// assert_that!(
39///     value,
40///     json::unordered_elements_are![
41///         eq("c"),
42///         eq("a"),
43///         eq("b"),
44///     ]
45/// );
46/// ```
47///
48/// This fails because the input is not an array:
49/// ```should_panic
50/// # use googletest::prelude::*;
51/// # use serde_json::json;
52/// # use crate::googletest_json_serde::json;
53/// let value = json!("not an array");
54/// assert_that!(
55///     value,
56///     json::unordered_elements_are![
57///         eq("a"),
58///         eq("b"),
59///     ]
60/// );
61/// ```
62///
63/// # Alias
64///
65/// This macro is re-exported as [`json::unordered_elements_are!`](crate::json::unordered_elements_are).
66#[macro_export]
67#[doc(hidden)]
68macro_rules! __json_unordered_elements_are {
69    ($(,)?) => {{
70        $crate::matchers::__internal_unstable_do_not_depend_on_these::
71        JsonUnorderedElementsAreMatcher::new(
72            vec![],
73            $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::PerfectMatch,
74        )
75    }};
76
77    ($($matcher:expr),* $(,)?) => {{
78        $crate::matchers::__internal_unstable_do_not_depend_on_these::
79        JsonUnorderedElementsAreMatcher::new(
80            vec![
81                $(
82                    $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($matcher)
83                ),*
84            ],
85            $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::PerfectMatch,
86        )
87    }};
88}
89
90/// Matches a JSON array that contains elements matched by the given matchers, in any order.
91///
92/// To match, each provided matcher must have a **distinct** corresponding element in the array.
93/// There may be **additional** elements in the array that do not correspond to any matcher.
94///
95/// Put another way, `json::contains_each![...]` succeeds if there is a subset of the actual JSON
96/// array that `json::unordered_elements_are![...]` would match.
97///
98/// The actual value must be a JSON array (`serde_json::Value::Array`). If the value is not an array,
99/// or if any matcher has no unique matching element, the match fails.
100///
101/// # Examples
102///
103/// ```
104/// # use googletest::prelude::*;
105/// # use serde_json::json;
106/// # use crate::googletest_json_serde::json;
107/// # fn should_pass() -> Result<()> {
108/// verify_that!(json!(["c", "b", "a"]), json::contains_each![eq("a"), eq("b")])?;   // Passes
109/// verify_that!(json!(["x", "y", "y"]), json::contains_each![eq("y"), eq("x")])?;   // Passes
110/// #     Ok(())
111/// # }
112/// # fn should_fail_1() -> Result<()> {
113/// verify_that!(json!(["a"]), json::contains_each![eq("a"), eq("b")])?;             // Fails: array too small
114/// #     Ok(())
115/// # }
116/// # fn should_fail_2() -> Result<()> {
117/// verify_that!(json!(["a", "b", "c"]), json::contains_each![eq("a"), eq("z")])?;    // Fails: second matcher unmatched
118/// #     Ok(())
119/// # }
120/// # fn should_fail_3() -> Result<()> {
121/// verify_that!(json!(["x", "x"]), json::contains_each![eq("x"), eq("x"), eq("x")])?; // Fails: no 1-1 mapping
122/// #     Ok(())
123/// # }
124/// # should_pass().unwrap();
125/// # should_fail_1().unwrap_err();
126/// # should_fail_2().unwrap_err();
127/// # should_fail_3().unwrap_err();
128/// ```
129#[macro_export]
130#[doc(hidden)]
131macro_rules! __json_contains_each {
132    ([$($matcher:expr),* $(,)?]) => {{
133        $crate::matchers::__internal_unstable_do_not_depend_on_these::
134        JsonUnorderedElementsAreMatcher::new(
135            vec![
136                $(
137                    $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($matcher)
138                ),*
139            ],
140            $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Superset,
141        )
142    }};
143    // Convenience: allow unbracketed list and forward to the bracketed arm.
144    ($($matcher:expr),* $(,)?) => {{
145        $crate::__json_contains_each!([$($matcher),*])
146    }};
147}
148/// Matches a JSON array where every element matches one of the provided matchers.
149///
150/// This macro succeeds if:
151/// - the input is a JSON array
152/// - every element in the array matches exactly one matcher
153/// - matchers are not reused
154/// - extra matchers may be provided and left unmatched
155/// - order does not matter
156///
157/// This macro fails if:
158/// - the input is not a JSON array
159/// - any element in the array fails to match all matchers
160///
161/// Accepts both bracketed (`json::is_contained_in!([ ... ])`) and unbracketed (`json::is_contained_in!(...)`) forms.
162///
163/// Example:
164/// ```
165/// # use googletest::prelude::*;
166/// # use serde_json::json as j;
167/// # use crate::googletest_json_serde::json;
168/// let value = j!(["a", "b", "c"]);
169/// assert_that!(
170///     value,
171///     json::is_contained_in![eq("a"), eq("b"), eq("c"), eq("d")]
172/// );
173/// ```
174///
175/// # How it works
176///
177/// - Each matcher can match at most one element
178/// - Extra matchers may remain unused
179/// - Every element in the array must be matched
180///
181/// # Alias
182///
183/// This macro is re-exported as [`json::is_contained_in!`](crate::json::is_contained_in).
184#[macro_export]
185#[doc(hidden)]
186macro_rules! __json_is_contained_in {
187    ([$($matcher:expr),* $(,)?]) => {{
188        $crate::matchers::__internal_unstable_do_not_depend_on_these::JsonUnorderedElementsAreMatcher::new(
189            vec![
190                $(
191                    $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($matcher)
192                ),*
193            ],
194            $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Subset,
195        )
196    }};
197    // Convenience: allow unbracketed list and forward to the bracketed arm.
198    ($($matcher:expr),* $(,)?) => {{
199        $crate::__json_is_contained_in!([$($matcher),*])
200    }};
201}
202#[doc(hidden)]
203pub mod internal {
204    use crate::matcher_support::match_matrix::internal::{MatchMatrix, Requirements};
205    use crate::matchers::json_matcher::internal::JsonMatcher;
206    use googletest::description::Description;
207    use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
208    use serde_json::Value;
209
210    #[doc(hidden)]
211    #[derive(MatcherBase)]
212    pub struct JsonUnorderedElementsAreMatcher {
213        elements: Vec<Box<dyn for<'a> Matcher<&'a Value>>>,
214        requirements: Requirements,
215    }
216    impl JsonMatcher for JsonUnorderedElementsAreMatcher {}
217
218    impl JsonUnorderedElementsAreMatcher {
219        pub fn new(
220            elements: Vec<Box<dyn for<'a> Matcher<&'a Value>>>,
221            requirements: Requirements,
222        ) -> Self {
223            Self {
224                elements,
225                requirements,
226            }
227        }
228    }
229
230    impl Matcher<&Value> for JsonUnorderedElementsAreMatcher {
231        fn matches(&self, actual: &Value) -> MatcherResult {
232            let Value::Array(actual_array) = actual else {
233                return MatcherResult::NoMatch;
234            };
235            let matrix = MatchMatrix::generate(actual_array, &self.elements);
236            matrix.is_match_for(self.requirements).into()
237        }
238
239        fn describe(&self, result: MatcherResult) -> Description {
240            let inner: Description = self
241                .elements
242                .iter()
243                .map(|m| m.describe(MatcherResult::Match))
244                .collect();
245            let inner = inner.enumerate().indent();
246            let header = if result.into() {
247                "contains JSON array elements matching in any order:"
248            } else {
249                "doesn't contain JSON array elements matching in any order:"
250            };
251            format!("{header}\n{inner}").into()
252        }
253
254        fn explain_match(&self, actual: &Value) -> Description {
255            match actual {
256                Value::Array(actual_array) => {
257                    if let Some(size_msg) = self
258                        .requirements
259                        .explain_size_mismatch(actual_array, self.elements.len())
260                    {
261                        return size_msg;
262                    }
263                    let matrix = MatchMatrix::generate(actual_array, &self.elements);
264                    if let Some(unmatchable) = matrix.explain_unmatchable(self.requirements) {
265                        return unmatchable;
266                    }
267                    let best = matrix.find_best_match();
268                    best.get_explanation(actual_array, &self.elements, self.requirements)
269                        .unwrap_or("whose elements all match".into())
270                }
271                _ => "which is not a JSON array".into(),
272            }
273        }
274    }
275}