1#[macro_export]
18macro_rules! assert_jm {
19 ($actual:expr, { $($json:tt)* }) => {{
21 let actual = &$actual;
22 let expectation = $crate::create_json_matcher!({ $($json)* });
23 let errors = $crate::JsonMatcher::json_matches(&expectation, &actual);
24 if !errors.is_empty() {
25 let bullets = errors
26 .into_iter()
27 .map(|e| format!(" - {}", e))
28 .collect::<Vec<String>>();
29 let error_message = format!("\nJson matcher failed:\n{}", bullets.join("\n"));
30 let actual_message = format!(
31 "Actual:\n{}",
32 serde_json::to_string_pretty(&actual).unwrap()
33 );
34 panic!("{}\n\n{}", error_message, actual_message);
35 }
36 }};
37
38 ($actual:expr, [ $($json:tt)* ]) => {{
40 let actual = &$actual;
41 let expectation = $crate::create_json_matcher!([ $($json)* ]);
42 let errors = $crate::JsonMatcher::json_matches(&expectation, &actual);
43 if !errors.is_empty() {
44 let bullets = errors
45 .into_iter()
46 .map(|e| format!(" - {}", e))
47 .collect::<Vec<String>>();
48 let error_message = format!("\nJson matcher failed:\n{}", bullets.join("\n"));
49 let actual_message = format!(
50 "Actual:\n{}",
51 serde_json::to_string_pretty(&actual).unwrap()
52 );
53 panic!("{}\n\n{}", error_message, actual_message);
54 }
55 }};
56
57 ($actual:expr, $literal:literal) => {{
59 let actual = &$actual;
60 let expectation = $crate::create_json_matcher!($literal);
61 let errors = $crate::JsonMatcher::json_matches(&expectation, &actual);
62 if !errors.is_empty() {
63 let bullets = errors
64 .into_iter()
65 .map(|e| format!(" - {}", e))
66 .collect::<Vec<String>>();
67 let error_message = format!("\nJson matcher failed:\n{}", bullets.join("\n"));
68 let actual_message = format!(
69 "Actual:\n{}",
70 serde_json::to_string_pretty(&actual).unwrap()
71 );
72 panic!("{}\n\n{}", error_message, actual_message);
73 }
74 }};
75
76 ($actual:expr, null) => {{
78 let actual = &$actual;
79 let expectation = $crate::create_json_matcher!(null);
80 let errors = $crate::JsonMatcher::json_matches(&expectation, &actual);
81 if !errors.is_empty() {
82 let bullets = errors
83 .into_iter()
84 .map(|e| format!(" - {}", e))
85 .collect::<Vec<String>>();
86 let error_message = format!("\nJson matcher failed:\n{}", bullets.join("\n"));
87 let actual_message = format!(
88 "Actual:\n{}",
89 serde_json::to_string_pretty(&actual).unwrap()
90 );
91 panic!("{}\n\n{}", error_message, actual_message);
92 }
93 }};
94
95 ($actual:expr, true) => {{
97 let actual = &$actual;
98 let expectation = $crate::create_json_matcher!(true);
99 let errors = $crate::JsonMatcher::json_matches(&expectation, &actual);
100 if !errors.is_empty() {
101 let bullets = errors
102 .into_iter()
103 .map(|e| format!(" - {}", e))
104 .collect::<Vec<String>>();
105 let error_message = format!("\nJson matcher failed:\n{}", bullets.join("\n"));
106 let actual_message = format!(
107 "Actual:\n{}",
108 serde_json::to_string_pretty(&actual).unwrap()
109 );
110 panic!("{}\n\n{}", error_message, actual_message);
111 }
112 }};
113
114 ($actual:expr, false) => {{
116 let actual = &$actual;
117 let expectation = $crate::create_json_matcher!(false);
118 let errors = $crate::JsonMatcher::json_matches(&expectation, &actual);
119 if !errors.is_empty() {
120 let bullets = errors
121 .into_iter()
122 .map(|e| format!(" - {}", e))
123 .collect::<Vec<String>>();
124 let error_message = format!("\nJson matcher failed:\n{}", bullets.join("\n"));
125 let actual_message = format!(
126 "Actual:\n{}",
127 serde_json::to_string_pretty(&actual).unwrap()
128 );
129 panic!("{}\n\n{}", error_message, actual_message);
130 }
131 }};
132
133 ($actual:expr, $expectation:expr) => {{
135 let actual = &$actual;
136 let expectation = &$expectation;
137 let errors = $crate::JsonMatcher::json_matches(expectation, &actual);
138 if !errors.is_empty() {
139 let bullets = errors
140 .into_iter()
141 .map(|e| format!(" - {}", e))
142 .collect::<Vec<String>>();
143 let error_message = format!("\nJson matcher failed:\n{}", bullets.join("\n"));
144 let actual_message = format!(
145 "Actual:\n{}",
146 serde_json::to_string_pretty(&actual).unwrap()
147 );
148 panic!("{}\n\n{}", error_message, actual_message);
149 }
150 }};
151}
152
153#[macro_export]
183macro_rules! create_json_matcher {
184 (null) => {
186 $crate::NullMatcher::new()
187 };
188
189 (true) => {
191 $crate::BooleanMatcher::exact(true)
192 };
193 (false) => {
194 $crate::BooleanMatcher::exact(false)
195 };
196
197 ($num:literal) => {{
199 let value = serde_json::json!($num);
201 value
202 }};
203
204 ($string:literal) => {
206 $crate::StringMatcher::new($string)
207 };
208
209 ([ $($item:tt),* $(,)? ]) => {
211 $crate::ArrayMatcher::new()
212 $(.element($crate::create_json_matcher!($item)))*
213 };
214
215 ({ $($json:tt)* }) => {
217 $crate::create_json_matcher!(@object {} $($json)*)
218 };
219
220 (@object {$($out:tt)*}) => {
223 $crate::ObjectMatcher::new() $($out)*
224 };
225 (@object {$($out:tt)*} $key:literal : { $($value:tt)* } , $($rest:tt)*) => {
227 $crate::create_json_matcher!(@object {$($out)* .field($key, $crate::create_json_matcher!({ $($value)* }))} $($rest)*)
228 };
229 (@object {$($out:tt)*} $key:literal : { $($value:tt)* }) => {
230 $crate::ObjectMatcher::new() $($out)* .field($key, $crate::create_json_matcher!({ $($value)* }))
231 };
232 (@object {$($out:tt)*} $key:literal : [ $($value:tt)* ] , $($rest:tt)*) => {
234 $crate::create_json_matcher!(@object {$($out)* .field($key, $crate::create_json_matcher!([ $($value)* ]))} $($rest)*)
235 };
236 (@object {$($out:tt)*} $key:literal : [ $($value:tt)* ]) => {
237 $crate::ObjectMatcher::new() $($out)* .field($key, $crate::create_json_matcher!([ $($value)* ]))
238 };
239 (@object {$($out:tt)*} $key:literal : null , $($rest:tt)*) => {
241 $crate::create_json_matcher!(@object {$($out)* .field($key, $crate::create_json_matcher!(null))} $($rest)*)
242 };
243 (@object {$($out:tt)*} $key:literal : null) => {
244 $crate::ObjectMatcher::new() $($out)* .field($key, $crate::create_json_matcher!(null))
245 };
246 (@object {$($out:tt)*} $key:literal : true , $($rest:tt)*) => {
247 $crate::create_json_matcher!(@object {$($out)* .field($key, $crate::create_json_matcher!(true))} $($rest)*)
248 };
249 (@object {$($out:tt)*} $key:literal : true) => {
250 $crate::ObjectMatcher::new() $($out)* .field($key, $crate::create_json_matcher!(true))
251 };
252 (@object {$($out:tt)*} $key:literal : false , $($rest:tt)*) => {
253 $crate::create_json_matcher!(@object {$($out)* .field($key, $crate::create_json_matcher!(false))} $($rest)*)
254 };
255 (@object {$($out:tt)*} $key:literal : false) => {
256 $crate::ObjectMatcher::new() $($out)* .field($key, $crate::create_json_matcher!(false))
257 };
258 (@object {$($out:tt)*} $key:literal : $value:literal , $($rest:tt)*) => {
260 $crate::create_json_matcher!(@object {$($out)* .field($key, $crate::create_json_matcher!($value))} $($rest)*)
261 };
262 (@object {$($out:tt)*} $key:literal : $value:literal) => {
263 $crate::ObjectMatcher::new() $($out)* .field($key, $crate::create_json_matcher!($value))
264 };
265 (@object {$($out:tt)*} $key:ident : null , $($rest:tt)*) => {
267 $crate::create_json_matcher!(@object {$($out)* .field(stringify!($key), $crate::create_json_matcher!(null))} $($rest)*)
268 };
269 (@object {$($out:tt)*} $key:ident : null) => {
270 $crate::ObjectMatcher::new() $($out)* .field(stringify!($key), $crate::create_json_matcher!(null))
271 };
272 (@object {$($out:tt)*} $key:ident : true , $($rest:tt)*) => {
273 $crate::create_json_matcher!(@object {$($out)* .field(stringify!($key), $crate::create_json_matcher!(true))} $($rest)*)
274 };
275 (@object {$($out:tt)*} $key:ident : true) => {
276 $crate::ObjectMatcher::new() $($out)* .field(stringify!($key), $crate::create_json_matcher!(true))
277 };
278 (@object {$($out:tt)*} $key:ident : false , $($rest:tt)*) => {
279 $crate::create_json_matcher!(@object {$($out)* .field(stringify!($key), $crate::create_json_matcher!(false))} $($rest)*)
280 };
281 (@object {$($out:tt)*} $key:ident : false) => {
282 $crate::ObjectMatcher::new() $($out)* .field(stringify!($key), $crate::create_json_matcher!(false))
283 };
284 (@object {$($out:tt)*} $key:ident : $value:literal , $($rest:tt)*) => {
286 $crate::create_json_matcher!(@object {$($out)* .field(stringify!($key), $crate::create_json_matcher!($value))} $($rest)*)
287 };
288 (@object {$($out:tt)*} $key:ident : $value:literal) => {
289 $crate::ObjectMatcher::new() $($out)* .field(stringify!($key), $crate::create_json_matcher!($value))
290 };
291 (@object {$($out:tt)*} $key:literal : $value:expr , $($rest:tt)*) => {
293 $crate::create_json_matcher!(@object {$($out)* .field($key, $value)} $($rest)*)
294 };
295 (@object {$($out:tt)*} $key:literal : $value:expr) => {
296 $crate::ObjectMatcher::new() $($out)* .field($key, $value)
297 };
298 (@object {$($out:tt)*} $key:ident : $value:expr , $($rest:tt)*) => {
299 $crate::create_json_matcher!(@object {$($out)* .field(stringify!($key), $value)} $($rest)*)
300 };
301 (@object {$($out:tt)*} $key:ident : $value:expr) => {
302 $crate::ObjectMatcher::new() $($out)* .field(stringify!($key), $value)
303 };
304
305 ($expr:expr) => {
307 $expr
308 };
309}
310
311#[cfg(test)]
312mod tests {
313 use crate::{assert_jm, create_json_matcher, test::catch_string_panic};
314 use crate::{AnyMatcher, JsonMatcher};
315 use serde_json::json;
316
317 struct UuidMatcher;
319
320 impl UuidMatcher {
321 fn new() -> Self {
322 Self
323 }
324 }
325
326 impl JsonMatcher for UuidMatcher {
327 fn json_matches(&self, value: &serde_json::Value) -> Vec<crate::JsonMatcherError> {
328 match value.as_str() {
329 Some(s) if s.len() == 36 && s.chars().filter(|&c| c == '-').count() == 4 => vec![],
330 Some(_) => vec![crate::JsonMatcherError::at_root(
331 "Expected valid UUID format",
332 )],
333 None => vec![crate::JsonMatcherError::at_root("Expected string for UUID")],
334 }
335 }
336 }
337
338 #[test]
339 fn test_assert_jm_with_json_syntax_success() {
340 let actual = json!({
341 "name": "John",
342 "age": 30,
343 "active": true
344 });
345
346 assert_jm!(actual, {
348 "name": "John",
349 "age": 30,
350 "active": true
351 });
352 }
353
354 #[test]
355 fn test_assert_jm_with_json_syntax_failure() {
356 assert_eq!(
358 catch_string_panic(|| assert_jm!(json!({
359 "name": "John",
360 "age": 30,
361 "active": true
362 }), {
363 "name": "John",
364 "age": 35,
365 "active": true
366 })),
367 r#"
368Json matcher failed:
369 - $.age: Expected integer 35 but got 30
370
371Actual:
372{
373 "name": "John",
374 "age": 30,
375 "active": true
376}"#
377 );
378 }
379
380 #[test]
381 fn test_assert_jm_with_matcher_expression_success() {
382 let actual = json!({
383 "id": "550e8400-e29b-41d4-a716-446655440000",
384 "name": "John"
385 });
386
387 assert_jm!(actual, {
389 "id": UuidMatcher::new(),
390 "name": "John"
391 });
392 }
393
394 #[test]
395 fn test_assert_jm_with_matcher_expression_failure_on_nested_matched() {
396 assert_eq!(
398 catch_string_panic(|| assert_jm!(json!({
399 "id": "bloop",
400 "name": "John"
401 }), {
402 "id": UuidMatcher::new(),
403 "name": "John"
404 })),
405 r#"
406Json matcher failed:
407 - $.id: Expected valid UUID format
408
409Actual:
410{
411 "id": "bloop",
412 "name": "John"
413}"#
414 );
415 }
416
417 #[test]
418 fn test_assert_jm_with_matcher_expression_failure_on_exact_match() {
419 assert_eq!(
421 catch_string_panic(|| assert_jm!(json!({
422 "id": "550e8400-e29b-41d4-a716-446655440000",
423 "name": "Jim"
424 }), {
425 "id": UuidMatcher::new(),
426 "name": "John"
427 })),
428 r#"
429Json matcher failed:
430 - $.name: Expected string "John" but got "Jim"
431
432Actual:
433{
434 "id": "550e8400-e29b-41d4-a716-446655440000",
435 "name": "Jim"
436}"#
437 );
438 }
439
440 #[test]
441 fn test_assert_jm_with_matcher_expression_failure_on_both() {
442 assert_eq!(
444 catch_string_panic(|| assert_jm!(json!({
445 "id": "bloop",
446 "name": "Jim"
447 }), {
448 "id": UuidMatcher::new(),
449 "name": "John"
450 })),
451 r#"
452Json matcher failed:
453 - $.id: Expected valid UUID format
454 - $.name: Expected string "John" but got "Jim"
455
456Actual:
457{
458 "id": "bloop",
459 "name": "Jim"
460}"#
461 );
462 }
463
464 #[test]
465 fn test_assert_jm_with_mixed_matchers() {
466 let actual = json!({
467 "id": "550e8400-e29b-41d4-a716-446655440000",
468 "name": "John",
469 "tags": ["admin", "user"],
470 "metadata": {
471 "created": "2023-01-01",
472 "version": 1
473 }
474 });
475
476 assert_jm!(actual, {
478 "id": UuidMatcher::new(),
479 "name": "John",
480 "tags": ["admin", "user"],
481 "metadata": {
482 "created": AnyMatcher::new(),
483 "version": 1
484 }
485 });
486 }
487
488 #[test]
489 fn test_assert_jm_failure_message() {
490 let actual = json!({
491 "name": "Jane",
492 "age": 25
493 });
494
495 let error_message = catch_string_panic(|| {
496 assert_jm!(actual, {
497 "name": "John",
498 "age": 25
499 });
500 });
501
502 assert!(error_message.contains("Json matcher failed"));
503 assert!(error_message.contains("Expected string \"John\" but got \"Jane\""));
504 }
505
506 #[test]
507 fn test_create_json_matcher_macro_directly() {
508 let matcher = create_json_matcher!({
509 "field1": "exact value",
510 "field2": AnyMatcher::new()
511 });
512
513 let valid_json = json!({
514 "field1": "exact value",
515 "field2": "anything"
516 });
517
518 assert_eq!(matcher.json_matches(&valid_json), vec![]);
519 }
520
521 #[test]
522 fn test_assert_jm_with_arrays() {
523 let actual = json!({
524 "items": [1, 2, 3],
525 "names": ["Alice", "Bob"]
526 });
527
528 assert_jm!(actual, {
529 "items": [1, 2, 3],
530 "names": ["Alice", "Bob"]
531 });
532 }
533
534 #[test]
535 fn test_assert_jm_nested_objects() {
536 let actual = json!({
537 "user": {
538 "profile": {
539 "name": "John",
540 "verified": true
541 }
542 }
543 });
544
545 assert_jm!(actual, {
546 "user": {
547 "profile": {
548 "name": "John",
549 "verified": true
550 }
551 }
552 });
553 }
554
555 #[test]
556 fn test_assert_jm_original_syntax_still_works() {
557 let actual = json!({
558 "value": "test"
559 });
560
561 let matcher = create_json_matcher!({
563 "value": "test"
564 });
565 assert_jm!(actual, matcher);
566
567 assert_jm!(actual, json!({"value": "test"}));
569 }
570
571 #[test]
572 fn test_assert_jm_direct_literals() {
573 assert_jm!(json!("hello"), "hello");
575
576 assert_jm!(json!(42), 42);
578
579 assert_jm!(json!(true), true);
581 assert_jm!(json!(false), false);
582
583 assert_jm!(json!(null), null);
585
586 assert_jm!(json!([1, 2, 3]), [1, 2, 3]);
588 }
589
590 #[test]
591 fn test_empty_object() {
592 assert_jm!(json!({}), {});
594
595 assert_jm!(json!({"empty": {}}), {
597 "empty": {}
598 });
599
600 assert_jm!(json!([{}]), [{}]);
602 }
603}