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}