googletest_json_serde/matchers/
path_matcher.rs1use crate::json::__internal_unstable_do_not_depend_on_these;
2use crate::matcher_support::path::{ParsedPaths, collect_paths, format_path, parse_expected_paths};
3use crate::matchers::__internal_unstable_do_not_depend_on_these::JsonPredicateMatcher;
4use googletest::description::Description;
5use serde_json::Value;
6use std::collections::BTreeSet;
7
8pub fn has_paths(paths: &[&str]) -> JsonPredicateMatcher<impl Fn(&Value) -> bool, String, String> {
10 let ParsedPaths { parsed, errors } = parse_expected_paths(paths);
11 let expected_set: BTreeSet<_> = parsed.iter().map(|p| p.segments.clone()).collect();
12 let errors_for_explain = errors.clone();
13 let expected_desc = format!(
14 "a JSON object containing paths {:?}",
15 parsed.iter().map(|p| &p.raw).collect::<Vec<_>>()
16 );
17 let negative_desc = format!(
18 "which is missing one of {:?}",
19 parsed.iter().map(|p| &p.raw).collect::<Vec<_>>()
20 );
21
22 JsonPredicateMatcher::new(
23 {
24 let expected_set = expected_set.clone();
25 let errors = errors.clone();
26 move |v| {
27 if !errors.is_empty() || !v.is_object() {
28 return false;
29 }
30 let actual = collect_paths(v);
31 expected_set.iter().all(|p| actual.contains(p))
32 }
33 },
34 expected_desc,
35 negative_desc,
36 )
37 .with_explain_fn(move |v| {
38 if !errors_for_explain.is_empty() {
39 return Description::new().text(format!(
40 "invalid paths {:?}",
41 errors_for_explain
42 .iter()
43 .map(|e| e.as_str())
44 .collect::<Vec<_>>()
45 ));
46 }
47 if !v.is_object() {
48 return __internal_unstable_do_not_depend_on_these::describe_json_type(v);
49 }
50 let actual = collect_paths(v);
51 let missing: BTreeSet<_> = expected_set.difference(&actual).cloned().collect();
52 if missing.is_empty() {
53 Description::new()
54 } else {
55 Description::new().text(format!(
56 "missing paths {:?}",
57 missing.iter().map(|p| format_path(p)).collect::<Vec<_>>()
58 ))
59 }
60 })
61}
62
63pub fn has_only_paths(
65 paths: &[&str],
66) -> JsonPredicateMatcher<impl Fn(&Value) -> bool, String, String> {
67 let ParsedPaths { parsed, errors } = parse_expected_paths(paths);
68 let expected_set: BTreeSet<_> = parsed.iter().map(|p| p.segments.clone()).collect();
69 let errors_for_explain = errors.clone();
70 let expected_desc = format!(
71 "a JSON object with exactly paths {:?}",
72 parsed.iter().map(|p| &p.raw).collect::<Vec<_>>()
73 );
74 let negative_desc = format!(
75 "which does not have exactly paths {:?}",
76 parsed.iter().map(|p| &p.raw).collect::<Vec<_>>()
77 );
78
79 JsonPredicateMatcher::new(
80 {
81 let expected_set = expected_set.clone();
82 let errors = errors.clone();
83 move |v| {
84 if !errors.is_empty() || !v.is_object() {
85 return false;
86 }
87 let actual = collect_paths(v);
88 actual == expected_set
89 }
90 },
91 expected_desc,
92 negative_desc,
93 )
94 .with_explain_fn(move |v| {
95 if !errors_for_explain.is_empty() {
96 return Description::new().text(format!(
97 "invalid paths {:?}",
98 errors_for_explain
99 .iter()
100 .map(|e| e.as_str())
101 .collect::<Vec<_>>()
102 ));
103 }
104 if !v.is_object() {
105 return __internal_unstable_do_not_depend_on_these::describe_json_type(v);
106 }
107 let actual = collect_paths(v);
108 let missing: BTreeSet<_> = expected_set.difference(&actual).cloned().collect();
109 let extra: BTreeSet<_> = actual.difference(&expected_set).cloned().collect();
110 match (!missing.is_empty(), !extra.is_empty()) {
111 (true, true) => Description::new()
112 .text(format!(
113 "missing paths {:?}",
114 missing.iter().map(|p| format_path(p)).collect::<Vec<_>>()
115 ))
116 .text(format!(
117 ", extra paths {:?}",
118 extra.iter().map(|p| format_path(p)).collect::<Vec<_>>()
119 )),
120 (true, false) => Description::new().text(format!(
121 "missing paths {:?}",
122 missing.iter().map(|p| format_path(p)).collect::<Vec<_>>()
123 )),
124 (false, true) => Description::new().text(format!(
125 "extra paths {:?}",
126 extra.iter().map(|p| format_path(p)).collect::<Vec<_>>()
127 )),
128 (false, false) => Description::new(),
129 }
130 })
131}