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 serde_json::Value;
6
7pub fn predicate<P>(
30 predicate: P,
31) -> JsonPredicateMatcher<
32 P,
33 __internal_unstable_do_not_depend_on_these::NoDescription,
34 __internal_unstable_do_not_depend_on_these::NoDescription,
35>
36where
37 P: Fn(&Value) -> bool,
38{
39 JsonPredicateMatcher::new(
40 predicate,
41 __internal_unstable_do_not_depend_on_these::NoDescription,
42 __internal_unstable_do_not_depend_on_these::NoDescription,
43 )
44}
45pub fn is_null() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
47 JsonPredicateMatcher::new(|v| v.is_null(), "JSON null", "which is not JSON null")
48 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
49}
50pub fn is_not_null() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
52 JsonPredicateMatcher::new(|v| !v.is_null(), "not JSON null", "which is JSON null")
53 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
54}
55
56#[deprecated(since = "0.2.2", note = "Use `is_not_null` instead")]
58pub fn any_value() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
59 JsonPredicateMatcher::new(|v| !v.is_null(), "any JSON value", "is not any JSON value")
60 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
61}
62
63pub fn is_string() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
65 JsonPredicateMatcher::new(
66 |v| v.is_string(),
67 "a JSON string",
68 "which is not a JSON string",
69 )
70 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
71}
72
73pub fn is_number() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
75 JsonPredicateMatcher::new(
76 |v| v.is_number(),
77 "a JSON number",
78 "which is not a JSON number",
79 )
80 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
81}
82
83pub fn is_boolean() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
85 JsonPredicateMatcher::new(
86 |v| v.is_boolean(),
87 "a JSON boolean",
88 "which is not a JSON boolean",
89 )
90 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
91}
92
93pub fn is_array() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
95 JsonPredicateMatcher::new(
96 |v| v.is_array(),
97 "a JSON array",
98 "which is not a JSON array",
99 )
100 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
101}
102
103pub fn is_object() -> JsonPredicateMatcher<impl Fn(&Value) -> bool, &'static str, &'static str> {
105 JsonPredicateMatcher::new(
106 |v| v.is_object(),
107 "a JSON object",
108 "which is not a JSON object",
109 )
110 .with_explain_fn(__internal_unstable_do_not_depend_on_these::describe_json_type)
111}
112
113#[doc(hidden)]
114pub mod internal {
115 use googletest::description::Description;
116 use googletest::matcher::MatcherResult::{Match, NoMatch};
117 use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
118 use serde_json::Value;
119
120 pub trait PredicateDescription {
122 fn to_description(self) -> String;
123 }
124
125 impl PredicateDescription for &'static str {
126 fn to_description(self) -> String {
127 self.to_string()
128 }
129 }
130
131 impl PredicateDescription for String {
132 fn to_description(self) -> String {
133 self
134 }
135 }
136
137 impl<F> PredicateDescription for F
138 where
139 F: Fn() -> String,
140 {
141 fn to_description(self) -> String {
142 self()
143 }
144 }
145 #[derive(Clone, Copy, Debug)]
147 pub struct NoDescription;
148 impl PredicateDescription for NoDescription {
149 fn to_description(self) -> String {
150 String::new()
151 }
152 }
153
154 type ExplainFn = Box<dyn Fn(&Value) -> Description>;
156
157 #[derive(MatcherBase)]
158 pub struct JsonPredicateMatcher<P, D1 = NoDescription, D2 = NoDescription>
159 where
160 P: Fn(&Value) -> bool,
161 D1: PredicateDescription,
162 D2: PredicateDescription,
163 {
164 predicate: P,
165 positive_description: D1,
166 negative_description: D2,
167 explain_fn: Option<ExplainFn>,
168 }
169
170 impl<P, D1, D2> JsonPredicateMatcher<P, D1, D2>
171 where
172 P: Fn(&Value) -> bool,
173 D1: PredicateDescription,
174 D2: PredicateDescription,
175 {
176 pub fn new(predicate: P, positive_description: D1, negative_description: D2) -> Self {
177 Self {
178 predicate,
179 positive_description,
180 negative_description,
181 explain_fn: None,
182 }
183 }
184
185 pub fn with_description<D1b, D2b>(
186 self,
187 positive_description: D1b,
188 negative_description: D2b,
189 ) -> JsonPredicateMatcher<P, D1b, D2b>
190 where
191 D1b: PredicateDescription,
192 D2b: PredicateDescription,
193 {
194 JsonPredicateMatcher {
195 predicate: self.predicate,
196 positive_description,
197 negative_description,
198 explain_fn: self.explain_fn,
199 }
200 }
201
202 pub fn with_explain_fn<F>(mut self, f: F) -> Self
203 where
204 F: Fn(&Value) -> Description + 'static,
205 {
206 self.explain_fn = Some(Box::new(f));
207 self
208 }
209 }
210
211 impl<P, D1, D2> Matcher<&Value> for JsonPredicateMatcher<P, D1, D2>
212 where
213 P: Fn(&Value) -> bool,
214 D1: PredicateDescription + Clone,
215 D2: PredicateDescription + Clone,
216 {
217 fn matches(&self, actual: &Value) -> MatcherResult {
218 if (self.predicate)(actual) {
219 Match
220 } else {
221 NoMatch
222 }
223 }
224
225 fn describe(&self, result: MatcherResult) -> Description {
226 let pos = self.positive_description.clone().to_description();
227 let neg = self.negative_description.clone().to_description();
228
229 match result {
230 Match if pos.is_empty() => "matches predicate".into(),
231 NoMatch if neg.is_empty() => "does not match predicate".into(),
232 Match => pos.into(),
233 NoMatch => neg.into(),
234 }
235 }
236
237 fn explain_match(&self, actual: &Value) -> Description {
238 if let Some(ref f) = self.explain_fn {
239 return f(actual);
240 }
241 Description::new().text("which does not match the predicate")
242 }
243 }
244 pub trait JsonMatcher: for<'a> Matcher<&'a Value> {}
246
247 pub trait IntoJsonMatcher<T> {
249 fn into_json_matcher(self) -> Box<dyn for<'a> Matcher<&'a Value>>;
250 }
251
252 impl<J> IntoJsonMatcher<()> for J
253 where
254 J: JsonMatcher + 'static,
255 {
256 fn into_json_matcher(self) -> Box<dyn for<'a> Matcher<&'a Value>> {
257 Box::new(self)
258 }
259 }
260
261 impl<P, D1, D2> IntoJsonMatcher<()> for JsonPredicateMatcher<P, D1, D2>
262 where
263 P: Fn(&Value) -> bool + 'static,
264 D1: PredicateDescription + Clone + 'static,
265 D2: PredicateDescription + Clone + 'static,
266 {
267 fn into_json_matcher(self) -> Box<dyn for<'a> Matcher<&'a Value>> {
268 Box::new(self)
269 }
270 }
271
272 pub fn describe_json_type(v: &Value) -> Description {
273 match v {
274 Value::Null => "which is a JSON null",
275 Value::String(_) => "which is a JSON string",
276 Value::Number(_) => "which is a JSON number",
277 Value::Bool(_) => "which is a JSON boolean",
278 Value::Array(_) => "which is a JSON array",
279 Value::Object(_) => "which is a JSON object",
280 }
281 .into()
282 }
283}