googletest_json_serde/matchers/
matches_pattern_matcher.rs1#[macro_export]
40#[doc(hidden)]
41macro_rules! __json_matches_pattern {
42 (@wrap_matcher $lit:literal) => {
43 $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::<
44 $crate::matchers::__internal_unstable_do_not_depend_on_these::Literal
45 >::into_json_matcher($lit)
46 };
47 (@wrap_matcher $expr:expr) => {
48 $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($expr)
49 };
50 ({ $($key:literal : $val:expr),* $(,)? }) => {{
52 let fields = vec![
53 $(
54 ($key,
55 $crate::__json_matches_pattern!(@wrap_matcher $val)
56 )
57 ),*
58 ];
59 $crate::matchers::__internal_unstable_do_not_depend_on_these::JsonObjectMatcher::new ( fields, true )
60 }};
61 ({ $($key:literal : $val:expr),* , .. }) => {{
63 let fields = vec![
64 $(
65 ($key,
66 $crate::__json_matches_pattern!(@wrap_matcher $val)
67 )
68 ),*
69 ];
70 $crate::matchers::__internal_unstable_do_not_depend_on_these::JsonObjectMatcher::new ( fields, false )
71 }};
72}
73
74#[doc(hidden)]
75pub mod internal {
76 use crate::matchers::json_matcher::internal::JsonMatcher;
77 use googletest::{
78 description::Description,
79 matcher::{Matcher, MatcherBase, MatcherResult},
80 };
81 use serde_json::{Map, Value};
82
83 type FieldMatcherPair = (&'static str, Box<dyn JsonMatcher>);
84 #[derive(MatcherBase)]
85 pub struct JsonObjectMatcher {
86 fields: Vec<FieldMatcherPair>,
87 strict: bool,
88 }
89
90 impl JsonMatcher for JsonObjectMatcher {}
91
92 impl JsonObjectMatcher {
93 pub fn new(fields: Vec<FieldMatcherPair>, strict: bool) -> Self {
94 Self { fields, strict }
95 }
96
97 fn collect_field_mismatches(&self, obj: &Map<String, Value>) -> Vec<String> {
98 let mut mismatches = Vec::new();
99 for (key, matcher) in &self.fields {
100 match obj.get(*key) {
101 Some(value) => {
102 if matcher.matches(value).is_no_match() {
103 mismatches.push(format!(
104 " field '{}': {}",
105 key,
106 matcher.explain_match(value)
107 ));
108 }
109 }
110 None => {
111 if !matcher.allows_missing() {
112 mismatches.push(format!(" field '{key}': was missing"));
113 }
114 }
115 }
116 }
117 mismatches
118 }
119
120 fn collect_unknown_fields(&self, obj: &Map<String, Value>) -> Vec<String> {
121 let mut unknown_fields = Vec::new();
122 for key in obj.keys() {
123 if !self
124 .fields
125 .iter()
126 .any(|(expected_key, _)| expected_key == key)
127 {
128 unknown_fields.push(format!(" unexpected field '{key}' present"));
129 }
130 }
131 unknown_fields
132 }
133 }
134
135 impl Matcher<&Value> for JsonObjectMatcher {
136 fn matches(&self, actual: &Value) -> MatcherResult {
137 let Value::Object(obj) = actual else {
138 return MatcherResult::NoMatch;
139 };
140
141 for (key, matcher) in &self.fields {
143 match obj.get(*key) {
144 Some(v) => {
145 if matcher.matches(v).is_no_match() {
146 return MatcherResult::NoMatch;
147 }
148 }
149 None => {
150 if !matcher.allows_missing() {
152 return MatcherResult::NoMatch;
153 }
154 }
155 }
156 }
157
158 if self.strict {
160 for actual_key in obj.keys() {
161 if !self
162 .fields
163 .iter()
164 .any(|(expected_key, _)| expected_key == actual_key)
165 {
166 return MatcherResult::NoMatch;
167 }
168 }
169 }
170
171 MatcherResult::Match
172 }
173
174 fn describe(&self, result: MatcherResult) -> Description {
175 if result.is_match() {
176 "has JSON object with expected fields".into()
177 } else {
178 let expected_fields = self
179 .fields
180 .iter()
181 .map(|(k, m)| format!(" '{}': {}", k, m.describe(MatcherResult::Match)))
182 .collect::<Vec<_>>()
183 .join("\n");
184 format!("expected JSON object with fields:\n{expected_fields}").into()
185 }
186 }
187 fn explain_match(&self, actual: &Value) -> Description {
188 match actual {
189 Value::Object(obj) => {
190 if obj.is_empty() && self.fields.iter().all(|(_, m)| m.allows_missing()) {
191 Description::new()
192 } else {
193 let mut mismatches = self.collect_field_mismatches(obj);
194
195 if self.strict {
196 let unknown_fields = self.collect_unknown_fields(obj);
197 mismatches.extend(unknown_fields);
198 }
199
200 if mismatches.is_empty() {
201 Description::new().text("all fields matched as expected")
202 } else if mismatches.len() == 1 {
203 Description::new().text(
204 mismatches
205 .into_iter()
206 .next()
207 .unwrap()
208 .trim_start()
209 .to_string(),
210 )
211 } else {
212 Description::new().text(format!(
213 "had {} field mismatches:\n{}",
214 mismatches.len(),
215 mismatches.join("\n")
216 ))
217 }
218 }
219 }
220 _ => Description::new().text(format!("was {actual} (expected object)")),
221 }
222 }
223 }
224
225 impl Matcher<&Option<Value>> for JsonObjectMatcher {
228 fn matches(&self, actual: &Option<Value>) -> MatcherResult {
229 match actual {
230 Some(v) => self.matches(v),
231 None => MatcherResult::NoMatch,
232 }
233 }
234
235 fn describe(&self, result: MatcherResult) -> Description {
236 if result.is_match() {
237 "has Some(JSON object) with expected fields".into()
238 } else {
239 let expected_fields = self
240 .fields
241 .iter()
242 .map(|(k, m)| format!(" '{}': {}", k, m.describe(MatcherResult::Match)))
243 .collect::<Vec<_>>()
244 .join("\n");
245 format!("expected Some(JSON object) with fields:\n{expected_fields}").into()
246 }
247 }
248
249 fn explain_match(&self, actual: &Option<Value>) -> Description {
250 match actual {
251 Some(value) => {
252 self.explain_match(value)
254 }
255 None => Description::new().text("was None (expected Some(JSON object))"),
256 }
257 }
258 }
259}