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