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 for<'a> Matcher<&'a Value>>);
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 mismatches.push(format!(" field '{key}': was missing"));
130 }
131 }
132 }
133 mismatches
134 }
135
136 fn collect_unknown_fields(&self, obj: &Map<String, Value>) -> Vec<String> {
137 let mut unknown_fields = Vec::new();
138 for key in obj.keys() {
139 if !self
140 .fields
141 .iter()
142 .any(|(expected_key, _)| expected_key == key)
143 {
144 unknown_fields.push(format!(" unexpected field '{key}' present"));
145 }
146 }
147 unknown_fields
148 }
149 }
150
151 impl Matcher<&Value> for JsonObjectMatcher {
152 fn matches(&self, actual: &Value) -> MatcherResult {
153 if let Value::Object(obj) = actual {
154 for (k, m) in &self.fields {
155 match obj.get(*k) {
156 Some(v) if m.matches(v).is_match() => (),
157 _ => return MatcherResult::NoMatch,
158 }
159 }
160 if self.strict && obj.len() != self.fields.len() {
161 return MatcherResult::NoMatch;
162 }
163 MatcherResult::Match
164 } else {
165 MatcherResult::NoMatch
166 }
167 }
168
169 fn describe(&self, result: MatcherResult) -> Description {
170 if result.is_match() {
171 "has JSON object with expected fields".into()
172 } else {
173 let expected_fields = self
174 .fields
175 .iter()
176 .map(|(k, m)| format!(" '{}': {}", k, m.describe(MatcherResult::Match)))
177 .collect::<Vec<_>>()
178 .join("\n");
179 format!("expected JSON object with fields:\n{expected_fields}").into()
180 }
181 }
182 fn explain_match(&self, actual: &Value) -> Description {
183 match actual {
184 Value::Object(obj) => {
185 let mut mismatches = self.collect_field_mismatches(obj);
186
187 if self.strict {
188 let unknown_fields = self.collect_unknown_fields(obj);
189 mismatches.extend(unknown_fields);
190 }
191
192 if mismatches.is_empty() {
193 Description::new().text("all fields matched as expected")
194 } else if mismatches.len() == 1 {
195 Description::new().text(
196 mismatches
197 .into_iter()
198 .next()
199 .unwrap()
200 .trim_start()
201 .to_string(),
202 )
203 } else {
204 Description::new().text(format!(
205 "had {} field mismatches:\n{}",
206 mismatches.len(),
207 mismatches.join("\n")
208 ))
209 }
210 }
211 _ => Description::new().text(format!("was {actual} (expected object)")),
212 }
213 }
214 }
215
216 impl Matcher<&Option<Value>> for JsonObjectMatcher {
219 fn matches(&self, actual: &Option<Value>) -> MatcherResult {
220 match actual {
221 Some(v) => self.matches(v),
222 None => MatcherResult::NoMatch,
223 }
224 }
225
226 fn describe(&self, result: MatcherResult) -> Description {
227 if result.is_match() {
228 "has Some(JSON object) with expected fields".into()
229 } else {
230 let expected_fields = self
231 .fields
232 .iter()
233 .map(|(k, m)| format!(" '{}': {}", k, m.describe(MatcherResult::Match)))
234 .collect::<Vec<_>>()
235 .join("\n");
236 format!("expected Some(JSON object) with fields:\n{expected_fields}").into()
237 }
238 }
239
240 fn explain_match(&self, actual: &Option<Value>) -> Description {
241 match actual {
242 Some(value) => {
243 self.explain_match(value)
245 }
246 None => Description::new().text("was None (expected Some(JSON object))"),
247 }
248 }
249 }
250}