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}