googletest_json_serde/matchers/unordered_elements_are_matcher.rs
1//! JSON matchers that ignore element order in arrays.
2//!
3//! # Examples
4//! ```rust
5//! # use googletest::prelude::*;
6//! # use googletest_json_serde::json;
7//! # use serde_json::json as j;
8//! assert_that!(
9//! j!(["b", "a", j!("c")]),
10//! json::unordered_elements_are!["a", j!("c"), starts_with("b")]
11//! );
12//! ```
13
14/// Matches a JSON array whose elements pair one-to-one with the provided matchers, ignoring order.
15///
16/// The array length must equal the matcher count.
17///
18/// # Examples
19///
20/// ```rust
21/// # use googletest::prelude::*;
22/// # use googletest_json_serde::json;
23/// # use serde_json::json as j;
24/// assert_that!(
25/// j!(["a", "b", j!("c")]),
26/// json::unordered_elements_are!["a", j!("c"), starts_with("b")]
27/// );
28/// ```
29///
30/// ```rust,should_panic
31/// # use googletest::prelude::*;
32/// # use googletest_json_serde::json;
33/// # use serde_json::json as j;
34/// assert_that!(
35/// j!(["a", "x", "c"]),
36/// json::unordered_elements_are![eq("c"), eq("a"), eq("b")]
37/// );
38/// ```
39///
40/// # Supported Inputs
41/// - Literal JSON-compatible values
42/// - Direct `serde_json::Value`
43/// - Native googletest matchers
44#[macro_export]
45#[doc(hidden)]
46macro_rules! __json_unordered_elements_are {
47 ($(,)?) => {{
48 $crate::matchers::__internal_unstable_do_not_depend_on_these::
49 JsonUnorderedElementsAreMatcher::new(
50 vec![],
51 $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::PerfectMatch,
52 )
53 }};
54
55 ($($matcher:expr),* $(,)?) => {{
56 $crate::matchers::__internal_unstable_do_not_depend_on_these::
57 JsonUnorderedElementsAreMatcher::new(
58 vec![
59 $(
60 $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($matcher)
61 ),*
62 ],
63 $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::PerfectMatch,
64 )
65 }};
66}
67
68/// Matches a JSON array that contains distinct matches for each provided matcher, ignoring order.
69///
70/// Extra array elements are allowed.
71///
72/// # Examples
73///
74/// ```rust
75/// # use googletest::prelude::*;
76/// # use googletest_json_serde::json;
77/// # use serde_json::json as j;
78/// verify_that!(
79/// j!(["alpha", "bingo", "c"]),
80/// json::contains_each!["c", j!("alpha"), starts_with("b")]
81/// )
82/// .unwrap();
83/// ```
84///
85/// ```rust,should_panic
86/// # use googletest::prelude::*;
87/// # use googletest_json_serde::json;
88/// # use serde_json::json;
89/// verify_that!(json!(["a"]), json::contains_each![eq("a"), eq("b")]).unwrap();
90/// ```
91///
92/// # Supported Inputs
93/// - Literal JSON-compatible values
94/// - Direct `serde_json::Value`
95/// - Native googletest matchers
96#[macro_export]
97#[doc(hidden)]
98macro_rules! __json_contains_each {
99 ([$($matcher:expr),* $(,)?]) => {{
100 $crate::matchers::__internal_unstable_do_not_depend_on_these::
101 JsonUnorderedElementsAreMatcher::new(
102 vec![
103 $(
104 $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($matcher)
105 ),*
106 ],
107 $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Superset,
108 )
109 }};
110 // Convenience: allow unbracketed list and forward to the bracketed arm.
111 ($($matcher:expr),* $(,)?) => {{
112 $crate::__json_contains_each!([$($matcher),*])
113 }};
114}
115/// Matches a JSON array where every element satisfies one of the provided matchers without reuse.
116///
117/// Matchers may remain unused; order is irrelevant.
118///
119/// # Examples
120///
121/// ```rust
122/// # use googletest::prelude::*;
123/// # use googletest_json_serde::json;
124/// # use serde_json::json as j;
125/// assert_that!(
126/// j!(["a", "b", j!("c")]),
127/// json::is_contained_in!["a", j!("c"), starts_with("b"), eq("d")]
128/// );
129/// ```
130///
131/// ```rust,should_panic
132/// # use googletest::prelude::*;
133/// # use googletest_json_serde::json;
134/// # use serde_json::json as j;
135/// assert_that!(
136/// j!(["a", "x"]),
137/// json::is_contained_in![eq("a"), eq("b")]
138/// );
139/// ```
140///
141/// # Supported Inputs
142/// - Literal JSON-compatible values
143/// - Direct `serde_json::Value`
144/// - Native googletest matchers
145#[macro_export]
146#[doc(hidden)]
147macro_rules! __json_is_contained_in {
148 ([$($matcher:expr),* $(,)?]) => {{
149 $crate::matchers::__internal_unstable_do_not_depend_on_these::JsonUnorderedElementsAreMatcher::new(
150 vec![
151 $(
152 $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($matcher)
153 ),*
154 ],
155 $crate::matchers::__internal_unstable_do_not_depend_on_these::Requirements::Subset,
156 )
157 }};
158 // Convenience: allow unbracketed list and forward to the bracketed arm.
159 ($($matcher:expr),* $(,)?) => {{
160 $crate::__json_is_contained_in!([$($matcher),*])
161 }};
162}
163#[doc(hidden)]
164pub mod internal {
165 use crate::matcher_support::match_matrix::internal::{MatchMatrix, Requirements};
166 use crate::matchers::json_matcher::internal::JsonMatcher;
167 use googletest::description::Description;
168 use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
169 use serde_json::Value;
170
171 #[doc(hidden)]
172 #[derive(MatcherBase)]
173 pub struct JsonUnorderedElementsAreMatcher {
174 elements: Vec<Box<dyn JsonMatcher>>,
175 requirements: Requirements,
176 }
177 impl JsonMatcher for JsonUnorderedElementsAreMatcher {}
178
179 impl JsonUnorderedElementsAreMatcher {
180 pub fn new(elements: Vec<Box<dyn JsonMatcher>>, requirements: Requirements) -> Self {
181 Self {
182 elements,
183 requirements,
184 }
185 }
186 }
187
188 impl Matcher<&Value> for JsonUnorderedElementsAreMatcher {
189 fn matches(&self, actual: &Value) -> MatcherResult {
190 let Value::Array(actual_array) = actual else {
191 return MatcherResult::NoMatch;
192 };
193 let matrix = MatchMatrix::generate(actual_array, &self.elements);
194 matrix.is_match_for(self.requirements).into()
195 }
196
197 fn describe(&self, result: MatcherResult) -> Description {
198 let inner: Description = self
199 .elements
200 .iter()
201 .map(|m| m.describe(MatcherResult::Match))
202 .collect();
203 let inner = inner.enumerate().indent();
204 let header = if result.into() {
205 "contains JSON array elements matching in any order:"
206 } else {
207 "doesn't contain JSON array elements matching in any order:"
208 };
209 format!("{header}\n{inner}").into()
210 }
211
212 fn explain_match(&self, actual: &Value) -> Description {
213 match actual {
214 Value::Array(actual_array) => {
215 if let Some(size_msg) = self
216 .requirements
217 .explain_size_mismatch(actual_array, self.elements.len())
218 {
219 return size_msg;
220 }
221 let matrix = MatchMatrix::generate(actual_array, &self.elements);
222 if let Some(unmatchable) = matrix.explain_unmatchable(self.requirements) {
223 return unmatchable;
224 }
225 let best = matrix.find_best_match();
226 best.get_explanation(actual_array, &self.elements, self.requirements)
227 .unwrap_or("whose elements all match".into())
228 }
229 _ => "which is not a JSON array".into(),
230 }
231 }
232 }
233}