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