1use serde_json::{Map, Value};
3use std::fmt::Debug;
4use std::mem::discriminant;
5
6pub trait Matcher: Send + Sync + Debug {
13 fn matches(&self, text: &str) -> bool;
14}
15
16#[derive(Debug)]
19pub struct Any {}
20
21impl Any {
22 pub fn new() -> Any {
23 Any {}
24 }
25}
26
27impl Default for Any {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl Matcher for Any {
34 fn matches(&self, _: &str) -> bool {
35 true
36 }
37}
38
39#[derive(Debug)]
84pub struct AnyThat {
85 f: fn(&str) -> bool,
86}
87
88impl AnyThat {
89 pub fn new(f: fn(&str) -> bool) -> AnyThat {
90 AnyThat { f }
91 }
92}
93
94impl Matcher for AnyThat {
95 fn matches(&self, text: &str) -> bool {
96 (self.f)(text)
97 }
98}
99
100#[derive(Debug)]
114pub struct StringContains<'a> {
115 string: &'a str,
116}
117
118impl<'a> StringContains<'a> {
119 pub fn new(string: &'a str) -> Self {
120 Self { string }
121 }
122}
123
124impl Matcher for StringContains<'_> {
125 fn matches(&self, text: &str) -> bool {
126 text.contains(self.string)
127 }
128}
129
130#[derive(Debug)]
132pub struct StringExact<'a> {
133 string: &'a str,
134}
135
136impl<'a> StringExact<'a> {
137 pub fn new(string: &'a str) -> Self {
138 Self { string }
139 }
140}
141
142impl Matcher for StringExact<'_> {
143 fn matches(&self, text: &str) -> bool {
144 text == self.string
145 }
146}
147
148#[derive(Debug)]
167pub struct JsonExact {
168 json: Value,
169}
170
171impl JsonExact {
172 pub fn new(json: Value) -> Self {
173 JsonExact { json }
174 }
175}
176
177impl Matcher for JsonExact {
178 fn matches(&self, text: &str) -> bool {
179 if let Ok(json) = serde_json::from_str::<Value>(text) {
180 json == self.json
181 } else {
182 false
183 }
184 }
185}
186
187#[derive(Debug)]
218pub struct JsonPartial {
219 pattern: Value,
220}
221
222impl JsonPartial {
223 pub fn new(pattern: Value) -> Self {
224 JsonPartial { pattern }
225 }
226
227 fn match_json(data: &Value, pattern: &Value) -> bool {
228 if discriminant(data) == discriminant(pattern) {
229 match pattern {
230 Value::Null => data.is_null(),
231 Value::Bool(b) => *b == data.as_bool().unwrap(),
232 Value::Number(n) => Some(n) == data.as_number(),
233 Value::String(s) => Some(s.as_str()) == data.as_str(),
234 Value::Array(a) => Some(a) == data.as_array(),
235 Value::Object(o) => Self::match_object(data.as_object().unwrap(), o),
236 }
237 } else {
238 false
239 }
240 }
241
242 fn match_object(data: &Map<String, Value>, pattern: &Map<String, Value>) -> bool {
243 pattern.keys().all(|k| {
244 data.contains_key(k) && Self::match_json(data.get(k).unwrap(), pattern.get(k).unwrap())
245 })
246 }
247}
248
249impl Matcher for JsonPartial {
250 fn matches(&self, text: &str) -> bool {
251 if let Ok(json) = serde_json::from_str::<Value>(text) {
252 Self::match_json(&json, &self.pattern)
253 } else {
254 false
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use crate::matchers::{
262 Any, AnyThat, JsonExact, JsonPartial, Matcher, StringContains, StringExact,
263 };
264 use serde_json;
265 use serde_json::{Value, json};
266 use std::str::FromStr;
267
268 #[test]
269 fn any_matches_anything() {
270 let matcher = Any::default();
272
273 assert!(matcher.matches(""));
274 assert!(matcher.matches("[42]"));
275 assert!(matcher.matches("AnyText"));
276 }
277
278 #[test]
279 fn string_contains() {
280 let matcher = StringContains::new("heartbeat");
281 let matching_message = "heartbeats keep websockets alive";
282 let non_matching_message = "typos don't match: heatbeat";
283
284 assert!(matcher.matches(matching_message));
285 assert!(!matcher.matches(non_matching_message));
286 }
287
288 #[test]
289 fn string_exact() {
290 let matcher = StringExact::new("typos");
291 let matching_message = "typos";
292 let non_matching_message = "typo";
293 let non_matching_message_2 = "typographical issue";
294
295 assert!(matcher.matches(matching_message));
296 assert!(!matcher.matches(non_matching_message));
297 assert!(!matcher.matches(non_matching_message_2));
298 }
299
300 #[test]
301 fn any_that() {
302 let matcher = AnyThat::new(|text| text.contains(' '));
303 let matching_message = "contains spaces";
304 let non_matching_message = "doesNotContainSpaces";
305
306 assert!(matcher.matches(matching_message));
307 assert!(!matcher.matches(non_matching_message));
308 }
309
310 #[test]
311 fn any_that_parses_to_i64() {
312 let matcher = AnyThat::new(|text| i64::from_str(text).is_ok());
313 let matching_message = "42";
314 let invalid_message = "...";
315 let non_matching_message = "42.000001";
316
317 assert!(matcher.matches(matching_message));
318 assert!(!matcher.matches(invalid_message));
319 assert!(!matcher.matches(non_matching_message));
320 }
321
322 #[test]
323 fn json_exact_matches_only_exact_json() {
324 let expected_json = json!(["A", "B"]);
325 let unexpected_json = json!(["A", "B", "Z"]);
326 let serialized_expected = serde_json::to_string(&expected_json).unwrap();
327 let serialized_unexpected = serde_json::to_string(&unexpected_json).unwrap();
328
329 let matcher = JsonExact::new(expected_json);
330
331 assert!(matcher.matches(serialized_expected.as_str()));
332 assert!(!matcher.matches(serialized_unexpected.as_str()));
333 }
334
335 #[test]
336 fn json_exact_does_not_match_on_invalid_data() {
337 let expected_json = json!({});
338 let matcher = JsonExact::new(expected_json);
339
340 assert!(!matcher.matches("-"));
341 }
342
343 #[test]
344 fn json_partial_does_not_match_on_invalid_data() {
345 let expected_json = json!({});
346 let matcher = JsonPartial::new(expected_json);
347
348 assert!(!matcher.matches("-"));
349 }
350
351 #[test]
352 fn json_partial_null() {
353 let data = json!(null);
354 let other = json!("someString");
355
356 assert_partial_matching_and_non_matching(&data, &other);
357 }
358
359 #[test]
360 fn json_partial_string() {
361 let data = json!("someString");
362 let other = json!("someOtherString");
363
364 assert_partial_matching_and_non_matching(&data, &other);
365 }
366
367 #[test]
368 fn json_partial_number() {
369 let data = json!(42);
370 let other = json!(17);
371
372 assert_partial_matching_and_non_matching(&data, &other);
373 }
374
375 #[test]
376 fn json_partial_bool() {
377 let data = json!(true);
378 let other = json!(false);
379
380 assert_partial_matching_and_non_matching(&data, &other);
381 }
382
383 #[test]
384 fn json_partial_arrays() {
385 let data = json!([1, 2, 3]);
386 let other = json!([1, 2, 3, 4]);
387
388 assert_partial_matching_and_non_matching(&data, &other);
389 }
390
391 #[test]
392 fn json_partial_object_matching() {
393 let data = json!({"a": 0, "b": [1, 2]});
394 let matching_pattern = json!({"a": 0});
395
396 let matcher = JsonPartial::new(matching_pattern.clone());
397
398 let serialized_exact = serde_json::to_string(&matching_pattern).unwrap();
399 let serialized_partial = serde_json::to_string(&data).unwrap();
400
401 assert!(matcher.matches(&serialized_exact));
402 assert!(matcher.matches(&serialized_partial));
403 }
404
405 #[test]
406 fn json_partial_object_non_matching() {
407 let data = json!({"a": 0, "b": [1, 2]});
408 let non_matching_pattern = json!({"a": 0, "c": 1});
409
410 let matcher = JsonPartial::new(non_matching_pattern.clone());
411
412 let serialized_exact = serde_json::to_string(&non_matching_pattern).unwrap();
413 let serialized_unexpected = serde_json::to_string(&data).unwrap();
414
415 assert!(matcher.matches(&serialized_exact));
416 assert!(!matcher.matches(&serialized_unexpected));
417 }
418
419 fn assert_partial_matching_and_non_matching(data: &Value, other: &Value) {
420 let matcher = JsonPartial::new(data.clone());
421
422 let serialized_expected = serde_json::to_string(&data).unwrap();
423 let serialized_unexpected = serde_json::to_string(&other).unwrap();
424
425 assert!(matcher.matches(&serialized_expected));
426 assert!(!matcher.matches(&serialized_unexpected));
427 }
428}