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