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