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}