googletest_json_serde/matchers/
json_matcher.rs1use crate::json::__internal_unstable_do_not_depend_on_these;
4use crate::matchers::__internal_unstable_do_not_depend_on_these::JsonPredicateMatcher;
5use googletest::description::Description;
6use serde_json::Value;
7
8pub fn predicate<P>(
31 predicate: P,
32) -> JsonPredicateMatcher<
33 P,
34 __internal_unstable_do_not_depend_on_these::NoDescription,
35 __internal_unstable_do_not_depend_on_these::NoDescription,
36>
37where
38 P: Fn(&Value) -> bool,
39{
40 JsonPredicateMatcher::new(
41 predicate,
42 __internal_unstable_do_not_depend_on_these::NoDescription,
43 __internal_unstable_do_not_depend_on_these::NoDescription,
44 )
45}
46pub fn is_null() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
48 JsonPredicateMatcher::new(|v| v.is_null(), "JSON null", "which is not JSON null")
49 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
50}
51pub fn is_not_null() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
53 JsonPredicateMatcher::new(|v| !v.is_null(), "not JSON null", "which is JSON null")
54 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
55}
56
57#[deprecated(since = "0.2.2", note = "Use `is_not_null` instead")]
59pub fn any_value() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
60 JsonPredicateMatcher::new(|v| !v.is_null(), "any JSON value", "is not any JSON value")
61 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
62}
63
64pub fn is_string() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
66 JsonPredicateMatcher::new(
67 |v| v.is_string(),
68 "a JSON string",
69 "which is not a JSON string",
70 )
71 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
72}
73
74pub fn is_number() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
76 JsonPredicateMatcher::new(
77 |v| v.is_number(),
78 "a JSON number",
79 "which is not a JSON number",
80 )
81 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
82}
83
84pub fn is_boolean() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
86 JsonPredicateMatcher::new(
87 |v| v.is_boolean(),
88 "a JSON boolean",
89 "which is not a JSON boolean",
90 )
91 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
92}
93
94pub fn is_array() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
96 JsonPredicateMatcher::new(
97 |v| v.is_array(),
98 "a JSON array",
99 "which is not a JSON array",
100 )
101 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
102}
103
104pub fn is_empty_array() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str>
106{
107 JsonPredicateMatcher::new(
108 |v| v.as_array().is_some_and(|a| a.is_empty()),
109 "an empty JSON array",
110 "which is not an empty JSON array",
111 )
112 .with_explain_fn(|v| {
113 if v.is_array() {
114 Description::new().text("which is a non-empty JSON array")
115 } else {
116 __internal_unstable_do_not_depend_on_these::describe_json_type(v)
117 }
118 })
119}
120
121pub fn is_object() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
123 JsonPredicateMatcher::new(
124 |v| v.is_object(),
125 "a JSON object",
126 "which is not a JSON object",
127 )
128 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
129}
130
131#[doc(hidden)]
132pub mod internal {
133 use googletest::description::Description;
134 use googletest::matcher::MatcherResult::{Match, NoMatch};
135 use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
136 use serde_json::Value;
137
138 pub trait PredicateDescription {
140 fn to_description(self) -> String;
141 }
142
143 impl PredicateDescription for &'static str {
144 fn to_description(self) -> String {
145 self.to_string()
146 }
147 }
148
149 impl PredicateDescription for String {
150 fn to_description(self) -> String {
151 self
152 }
153 }
154
155 impl<F> PredicateDescription for F
156 where
157 F: Fn() -> String,
158 {
159 fn to_description(self) -> String {
160 self()
161 }
162 }
163 #[derive(Clone, Copy, Debug)]
165 pub struct NoDescription;
166 impl PredicateDescription for NoDescription {
167 fn to_description(self) -> String {
168 String::new()
169 }
170 }
171
172 type ExplainFn = Box<dyn Fn(&Value) -> Description>;
174
175 #[derive(MatcherBase)]
176 pub struct JsonPredicateMatcher<P, D1 = NoDescription, D2 = NoDescription>
177 where
178 P: Fn(&Value) -> bool,
179 D1: PredicateDescription,
180 D2: PredicateDescription,
181 {
182 predicate: P,
183 positive_description: D1,
184 negative_description: D2,
185 explain_fn: Option<ExplainFn>,
186 }
187
188 impl<P, D1, D2> JsonPredicateMatcher<P, D1, D2>
189 where
190 P: Fn(&Value) -> bool,
191 D1: PredicateDescription,
192 D2: PredicateDescription,
193 {
194 pub fn new(predicate: P, positive_description: D1, negative_description: D2) -> Self {
195 Self {
196 predicate,
197 positive_description,
198 negative_description,
199 explain_fn: None,
200 }
201 }
202
203 pub fn with_description<D1b, D2b>(
204 self,
205 positive_description: D1b,
206 negative_description: D2b,
207 ) -> JsonPredicateMatcher<P, D1b, D2b>
208 where
209 D1b: PredicateDescription,
210 D2b: PredicateDescription,
211 {
212 JsonPredicateMatcher {
213 predicate: self.predicate,
214 positive_description,
215 negative_description,
216 explain_fn: self.explain_fn,
217 }
218 }
219
220 pub fn with_explain_fn<F>(mut self, f: F) -> Self
221 where
222 F: Fn(&Value) -> Description + 'static,
223 {
224 self.explain_fn = Some(Box::new(f));
225 self
226 }
227 }
228
229 impl<P, D1, D2> Matcher<&Value> for JsonPredicateMatcher<P, D1, D2>
230 where
231 P: Fn(&Value) -> bool,
232 D1: PredicateDescription + Clone,
233 D2: PredicateDescription + Clone,
234 {
235 fn matches(&self, actual: &Value) -> MatcherResult {
236 if (self.predicate)(actual) {
237 Match
238 } else {
239 NoMatch
240 }
241 }
242
243 fn describe(&self, result: MatcherResult) -> Description {
244 let pos = self.positive_description.clone().to_description();
245 let neg = self.negative_description.clone().to_description();
246
247 match result {
248 Match if pos.is_empty() => "matches predicate".into(),
249 NoMatch if neg.is_empty() => "does not match predicate".into(),
250 Match => pos.into(),
251 NoMatch => neg.into(),
252 }
253 }
254
255 fn explain_match(&self, actual: &Value) -> Description {
256 if let Some(ref f) = self.explain_fn {
257 return f(actual);
258 }
259 Description::new().text("which does not match the predicate")
260 }
261 }
262 pub trait JsonMatcher: for<'a> Matcher<&'a Value> {
264 fn allows_missing(&self) -> bool {
266 false
267 }
268 }
269
270 pub trait IntoJsonMatcher<T> {
272 fn into_json_matcher(self) -> Box<dyn JsonMatcher>;
273 }
274
275 impl<J> IntoJsonMatcher<()> for J
276 where
277 J: JsonMatcher + 'static,
278 {
279 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
280 Box::new(self)
281 }
282 }
283
284 #[derive(googletest::matcher::MatcherBase)]
288 struct JsonEqMatcher {
289 expected: Value,
290 }
291
292 impl Matcher<&Value> for JsonEqMatcher {
293 fn matches(&self, actual: &Value) -> MatcherResult {
294 if *actual == self.expected {
295 Match
296 } else {
297 NoMatch
298 }
299 }
300
301 fn describe(&self, result: MatcherResult) -> Description {
302 match result {
303 Match => format!("is equal to {:?}", self.expected).into(),
304 NoMatch => format!("isn't equal to {:?}", self.expected).into(),
305 }
306 }
307
308 fn explain_match(&self, _actual: &Value) -> Description {
309 format!("which isn't equal to {:?}", self.expected).into()
311 }
312 }
313
314 impl JsonMatcher for JsonEqMatcher {}
315
316 impl IntoJsonMatcher<Value> for &Value {
318 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
319 Box::new(JsonEqMatcher {
320 expected: self.clone(),
321 })
322 }
323 }
324
325 impl IntoJsonMatcher<Value> for Value {
326 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
327 Box::new(JsonEqMatcher { expected: self })
328 }
329 }
330
331 pub struct Literal;
333
334 impl IntoJsonMatcher<Literal> for &str {
335 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
336 Box::new(JsonEqMatcher {
337 expected: Value::from(self),
338 })
339 }
340 }
341
342 impl IntoJsonMatcher<Literal> for String {
343 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
344 Box::new(JsonEqMatcher {
345 expected: Value::from(self),
346 })
347 }
348 }
349
350 impl IntoJsonMatcher<Literal> for bool {
351 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
352 Box::new(JsonEqMatcher {
353 expected: Value::from(self),
354 })
355 }
356 }
357
358 impl IntoJsonMatcher<Literal> for i64 {
359 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
360 Box::new(JsonEqMatcher {
361 expected: Value::from(self),
362 })
363 }
364 }
365 impl IntoJsonMatcher<Literal> for i32 {
366 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
367 Box::new(JsonEqMatcher {
368 expected: Value::from(self),
369 })
370 }
371 }
372
373 impl IntoJsonMatcher<Literal> for u64 {
374 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
375 Box::new(JsonEqMatcher {
376 expected: Value::from(self),
377 })
378 }
379 }
380
381 impl IntoJsonMatcher<Literal> for f64 {
382 fn into_json_matcher(self) -> Box<dyn JsonMatcher> {
383 Box::new(JsonEqMatcher {
384 expected: Value::from(self),
385 })
386 }
387 }
388
389 impl<P, D1, D2> JsonMatcher for JsonPredicateMatcher<P, D1, D2>
390 where
391 P: Fn(&Value) -> bool + 'static,
392 D1: PredicateDescription + Clone + 'static,
393 D2: PredicateDescription + Clone + 'static,
394 {
395 }
396
397 pub fn describe_json_type(v: &Value) -> Description {
398 match v {
399 Value::Null => "which is a JSON null",
400 Value::String(_) => "which is a JSON string",
401 Value::Number(_) => "which is a JSON number",
402 Value::Bool(_) => "which is a JSON boolean",
403 Value::Array(_) => "which is a JSON array",
404 Value::Object(_) => "which is a JSON object",
405 }
406 .into()
407 }
408}