json_matcher/matchers/
array.rs

1use serde_json::Value;
2
3use crate::{JsonMatcher, JsonMatcherError, JsonPath, JsonPathElement};
4
5pub struct ArrayMatcherRefs<'a> {
6    elements: Vec<&'a dyn JsonMatcher>,
7}
8
9impl<'a> ArrayMatcherRefs<'a> {
10    pub fn new(elements: Vec<&'a dyn JsonMatcher>) -> Self {
11        Self { elements }
12    }
13}
14
15impl JsonMatcher for ArrayMatcherRefs<'_> {
16    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
17        let mut errors: Vec<JsonMatcherError> = vec![];
18        match value {
19            Value::Array(array) => {
20                let actual_length = array.len();
21                let expected_length = self.elements.len();
22                let expected_but_missing_indexes = actual_length..expected_length;
23                if !expected_but_missing_indexes.is_empty() {
24                    let min = expected_but_missing_indexes
25                        .clone()
26                        .min()
27                        .expect("Expected array length is greater than 0");
28                    let max = expected_but_missing_indexes
29                        .max()
30                        .expect("Expected array length is greater than 0");
31                    let error = if min == max {
32                        format!("Array is missing index {}", min)
33                    } else {
34                        format!("Array is missing indexes: {}..{}", min, max)
35                    };
36                    errors.push(JsonMatcherError::at_root(error));
37                }
38                let unexpected_indexes = expected_length..actual_length;
39                if !unexpected_indexes.is_empty() {
40                    let min = unexpected_indexes
41                        .clone()
42                        .min()
43                        .expect("Unexpected array length is greater than 0");
44                    let max = unexpected_indexes
45                        .max()
46                        .expect("Unexpected array length is greater than 0");
47                    let error = if min == max {
48                        format!("Array has unexpected index {}", min)
49                    } else {
50                        format!("Array has unexpected indexes: {}..{}", min, max)
51                    };
52                    errors.push(JsonMatcherError::at_root(error));
53                }
54                let expected_and_present_indexes = 0..([actual_length, expected_length]
55                    .into_iter()
56                    .min()
57                    .expect("Array is inlined to have length greater than 0"));
58                for index in expected_and_present_indexes {
59                    let matcher = &self.elements[index];
60                    let value = array.get(index).expect("Index in array checked.");
61                    let sub_errors = matcher.json_matches(value);
62                    for sub_error in sub_errors {
63                        let this_path = JsonPath::from(vec![
64                            JsonPathElement::Root,
65                            JsonPathElement::Index(index),
66                        ]);
67                        let JsonMatcherError { path, message } = sub_error;
68                        let new_path = this_path.extend(path);
69                        errors.push(JsonMatcherError {
70                            path: new_path,
71                            message,
72                        });
73                    }
74                }
75            }
76            _ => errors.push(JsonMatcherError::at_root("Value is not an array")),
77        }
78        errors
79    }
80}
81
82pub struct ArrayMatcher {
83    elements: Vec<Box<dyn JsonMatcher>>,
84}
85
86impl Default for ArrayMatcher {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92impl ArrayMatcher {
93    pub fn new() -> Self {
94        Self { elements: vec![] }
95    }
96
97    pub fn of(elements: Vec<Box<dyn JsonMatcher>>) -> Self {
98        Self { elements }
99    }
100
101    pub fn element(mut self, value: impl JsonMatcher + 'static) -> Self {
102        self.elements.push(Box::new(value));
103        self
104    }
105}
106
107impl JsonMatcher for ArrayMatcher {
108    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
109        ArrayMatcherRefs::new(
110            self.elements
111                .iter()
112                .map(|x| x.as_ref() as &dyn JsonMatcher)
113                .collect(),
114        )
115        .json_matches(value)
116    }
117}
118
119impl JsonMatcher for Vec<Box<dyn JsonMatcher>> {
120    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
121        ArrayMatcherRefs::new(
122            self.iter()
123                .map(|x| x.as_ref() as &dyn JsonMatcher)
124                .collect(),
125        )
126        .json_matches(value)
127    }
128}
129
130impl JsonMatcher for [Box<dyn JsonMatcher>] {
131    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
132        ArrayMatcherRefs::new(
133            self.iter()
134                .map(|x| x.as_ref() as &dyn JsonMatcher)
135                .collect(),
136        )
137        .json_matches(value)
138    }
139}
140
141impl JsonMatcher for Vec<&dyn JsonMatcher> {
142    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
143        ArrayMatcherRefs::new(self.to_vec()).json_matches(value)
144    }
145}
146
147impl JsonMatcher for [&dyn JsonMatcher] {
148    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
149        ArrayMatcherRefs::new(self.to_vec()).json_matches(value)
150    }
151}
152
153impl<T: JsonMatcher> JsonMatcher for Vec<T> {
154    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
155        ArrayMatcherRefs::new(self.iter().map(|x| x as &dyn JsonMatcher).collect())
156            .json_matches(value)
157    }
158}
159
160impl<T: JsonMatcher> JsonMatcher for [T] {
161    fn json_matches(&self, value: &Value) -> Vec<JsonMatcherError> {
162        ArrayMatcherRefs::new(self.iter().map(|x| x as &dyn JsonMatcher).collect())
163            .json_matches(value)
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use serde_json::json;
170
171    use crate::test::catch_string_panic;
172    use crate::{assert_jm, StringMatcher};
173
174    use super::*;
175
176    #[test]
177    fn test_array_matcher() {
178        let get_matcher = || {
179            ArrayMatcher::new()
180                .element(
181                    ArrayMatcher::new()
182                        .element(StringMatcher::new("one"))
183                        .element(StringMatcher::new("two")),
184                )
185                .element(StringMatcher::new("three"))
186        };
187        // successful match
188        assert_jm!(json!([["one", "two"], "three"]), get_matcher());
189        // problem in a matcher under the root array
190        assert_eq!(
191            catch_string_panic(|| assert_jm!(json!([["one", "two"], "four"]), get_matcher())),
192            r#"
193Json matcher failed:
194  - $.1: Expected string "three" but got "four"
195
196Actual:
197[
198  [
199    "one",
200    "two"
201  ],
202  "four"
203]"#
204        );
205        // problem in a matcher under a nested array
206        assert_eq!(
207            catch_string_panic(|| assert_jm!(json!([["one", "four"], "three"]), get_matcher())),
208            r#"
209Json matcher failed:
210  - $.0.1: Expected string "two" but got "four"
211
212Actual:
213[
214  [
215    "one",
216    "four"
217  ],
218  "three"
219]"#
220        );
221        // unexpected index in root
222        assert_eq!(
223            catch_string_panic(|| assert_jm!(
224                json!([["one", "two"], "three", "four"]),
225                get_matcher()
226            )),
227            r#"
228Json matcher failed:
229  - $: Array has unexpected index 2
230
231Actual:
232[
233  [
234    "one",
235    "two"
236  ],
237  "three",
238  "four"
239]"#
240        );
241        // unexpected index in nested array
242        assert_eq!(
243            catch_string_panic(|| assert_jm!(
244                json!([["one", "two", "four"], "three"]),
245                get_matcher()
246            )),
247            r#"
248Json matcher failed:
249  - $.0: Array has unexpected index 2
250
251Actual:
252[
253  [
254    "one",
255    "two",
256    "four"
257  ],
258  "three"
259]"#
260        );
261        // multiple issues
262        assert_eq!(
263            catch_string_panic(|| assert_jm!(json!([[2], "three", "four", "five"]), get_matcher())),
264            r#"
265Json matcher failed:
266  - $: Array has unexpected indexes: 2..3
267  - $.0: Array is missing index 1
268  - $.0.0: Value is not a string
269
270Actual:
271[
272  [
273    2
274  ],
275  "three",
276  "four",
277  "five"
278]"#
279        );
280    }
281
282    #[test]
283    fn test_raw_implementations() {
284        let matcher: Vec<Box<dyn JsonMatcher>> = vec![Box::new(1), Box::new(2)];
285        assert_eq!(matcher.json_matches(&json!([1, 2])), vec![]);
286        assert_eq!(
287            matcher
288                .json_matches(&json!([1, 2, 3]))
289                .into_iter()
290                .map(|x| x.to_string())
291                .collect::<String>(),
292            "$: Array has unexpected index 2"
293        );
294        let matcher: [Box<dyn JsonMatcher>; 2] = [Box::new(1), Box::new(2)];
295        assert_eq!(matcher.json_matches(&json!([1, 2])), vec![]);
296        assert_eq!(
297            matcher
298                .json_matches(&json!([1, 2, 3]))
299                .into_iter()
300                .map(|x| x.to_string())
301                .collect::<String>(),
302            "$: Array has unexpected index 2"
303        );
304        let matcher: Vec<&dyn JsonMatcher> = vec![&1, &2];
305        assert_eq!(matcher.json_matches(&json!([1, 2])), vec![]);
306        assert_eq!(
307            matcher
308                .json_matches(&json!([1, 2, 3]))
309                .into_iter()
310                .map(|x| x.to_string())
311                .collect::<String>(),
312            "$: Array has unexpected index 2"
313        );
314        let matcher: [&dyn JsonMatcher; 2] = [&1, &2];
315        assert_eq!(matcher.json_matches(&json!([1, 2])), vec![]);
316        assert_eq!(
317            matcher
318                .json_matches(&json!([1, 2, 3]))
319                .into_iter()
320                .map(|x| x.to_string())
321                .collect::<String>(),
322            "$: Array has unexpected index 2"
323        );
324        let matcher: Vec<i8> = vec![1, 2];
325        assert_eq!(matcher.json_matches(&json!([1, 2])), vec![]);
326        assert_eq!(
327            matcher
328                .json_matches(&json!([1, 2, 3]))
329                .into_iter()
330                .map(|x| x.to_string())
331                .collect::<String>(),
332            "$: Array has unexpected index 2"
333        );
334        let matcher: [i8; 2] = [1, 2];
335        assert_eq!(matcher.json_matches(&json!([1, 2])), vec![]);
336        assert_eq!(
337            matcher
338                .json_matches(&json!([1, 2, 3]))
339                .into_iter()
340                .map(|x| x.to_string())
341                .collect::<String>(),
342            "$: Array has unexpected index 2"
343        );
344    }
345}