1use crate::JsonType;
2use crate::expect::ops::expect_object::ExpectObjectSubOp;
3use crate::expect_core::Context;
4use crate::expect_core::ExpectOp;
5use crate::expect_core::ExpectOpResult;
6use crate::expect_core::expect_op;
7use serde_json::Map;
8use serde_json::Value;
9
10#[expect_op(internal, name = "object")]
11#[derive(Debug, Clone, Default, PartialEq)]
12pub struct ExpectObject {
13 sub_ops: Vec<ExpectObjectSubOp>,
14}
15
16impl ExpectObject {
17 pub(crate) fn new() -> Self {
18 Self { sub_ops: vec![] }
19 }
20
21 pub fn empty(mut self) -> Self {
22 self.sub_ops.push(ExpectObjectSubOp::Empty);
23 self
24 }
25
26 pub fn not_empty(mut self) -> Self {
27 self.sub_ops.push(ExpectObjectSubOp::NotEmpty);
28 self
29 }
30
31 pub fn contains<V>(mut self, expected_values: V) -> Self
62 where
63 V: Into<Value>,
64 {
65 let value = Into::<Value>::into(expected_values);
66 let sub_op = match value {
67 Value::Object(values_object) => ExpectObjectSubOp::Contains(values_object),
68 _ => {
69 let value_type = JsonType::from(&value);
70 panic!("object().contains() expected to take object. Received: {value_type}");
71 }
72 };
73
74 self.sub_ops.push(sub_op);
75 self
76 }
77
78 #[doc(hidden)]
79 pub fn propagated_contains<V>(mut self, expected_values: V) -> Self
80 where
81 V: Into<Value>,
82 {
83 let value = Into::<Value>::into(expected_values);
84 let sub_op = match value {
85 Value::Object(values_object) => ExpectObjectSubOp::PartialContains(values_object),
86 _ => {
87 let value_type = JsonType::from(&value);
88 panic!(
89 "object().propagated_contains() expected to take object. Received: {value_type}"
90 );
91 }
92 };
93
94 self.sub_ops.push(sub_op);
95 self
96 }
97}
98
99impl ExpectOp for ExpectObject {
100 fn on_object(
101 &self,
102 context: &mut Context,
103 received: &Map<String, Value>,
104 ) -> ExpectOpResult<()> {
105 for sub_op in &self.sub_ops {
106 sub_op.on_object(self, context, received)?;
107 }
108
109 Ok(())
110 }
111
112 fn debug_supported_types(&self) -> &'static [JsonType] {
113 &[JsonType::Object]
114 }
115}
116
117#[cfg(test)]
118mod test_contains {
119 use crate::expect;
120 use crate::expect_json_eq;
121 use pretty_assertions::assert_eq;
122 use serde_json::json;
123
124 #[test]
125 fn it_should_be_equal_for_identical_objects() {
126 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
127 let right = json!(
128 expect::object().contains(json!({ "name": "John", "age": 30, "scores": [1, 2, 3] }))
129 );
130
131 let output = expect_json_eq(&left, &right);
132 assert!(output.is_ok());
133 }
134
135 #[test]
136 fn it_should_be_equal_for_reversed_identical_objects() {
137 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
138 let right = json!(
139 expect::object().contains(json!({ "scores": [1, 2, 3], "age": 30, "name": "John" }))
140 );
141
142 let output = expect_json_eq(&left, &right);
143 assert!(output.is_ok());
144 }
145
146 #[test]
147 fn it_should_be_equal_for_partial_contains() {
148 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
149 let right = json!(expect::object().contains(json!({ "name": "John", "age": 30 })));
150
151 let output = expect_json_eq(&left, &right);
152 assert!(output.is_ok());
153 }
154
155 #[test]
156 fn it_should_be_equal_for_nested_contains() {
157 let left = json!({ "name": "John", "comments": [
158 {
159 "text": "Hello",
160 "author": {
161 "name": "Jane",
162 "age": 25
163 }
164 },
165 {
166 "text": "Goodbye",
167 "author": {
168 "name": "John",
169 "age": 30
170 }
171 }
172 ]});
173
174 let right = json!(expect::object().contains(
175 json!({ "comments": expect::array().contains([
176 json!({
177 "text": "Hello",
178 "author": expect::object().contains(
179 json!({
180 "name": "Jane",
181 })
182 )
183 }),
184 ])})
185 ));
186
187 let output = expect_json_eq(&left, &right);
188 assert!(output.is_ok(), "{}", output.unwrap_err().to_string());
189 }
190
191 #[test]
192 fn it_should_error_for_same_fields_but_different_values() {
193 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
194 let right = json!(
195 expect::object().contains(json!({ "name": "Joe", "age": 31, "scores": [4, 5, 6] }))
196 );
197
198 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
199 assert_eq!(
200 output,
201 r#"Json integers at root.age are not equal:
202 expected 31
203 received 30"#
204 );
205 }
206
207 #[test]
208 fn it_should_be_ok_for_empty_contains() {
209 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
210 let right = json!(expect::object().contains(json!({})));
211
212 let output = expect_json_eq(&left, &right);
213 assert!(output.is_ok());
214 }
215
216 #[test]
217 fn it_should_be_ok_for_empty_on_empty_object() {
218 let left = json!({});
219 let right = json!(expect::object().contains(json!({})));
220
221 let output = expect_json_eq(&left, &right);
222 assert!(output.is_ok());
223 }
224
225 #[test]
226 fn it_should_error_if_used_against_the_wrong_type() {
227 let left = json!("🦊");
228 let right = json!(expect::object().contains(json!({})));
229
230 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
231 assert_eq!(
232 output,
233 r#"Json expect::object() at root, received wrong type:
234 expected object
235 received string "🦊""#
236 );
237 }
238
239 #[test]
240 fn it_should_error_if_nested_object_is_missing_fields() {
241 let left = json!({
242 "name": "John",
243 "id": "abd123",
244 "meta": {
245 "location": "uk",
246 "previous_login": "sometime last thursday",
247 }
248 });
249
250 let right = json!(expect::object().contains(json!({
251 "name": "John",
252 "meta": {},
253 })));
254
255 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
256 assert_eq!(
257 output,
258 r#"Json object at root.meta has many extra fields over expected:
259 expected {}
260 received {
261 "location": "uk",
262 "previous_login": "sometime last thursday"
263 }
264
265 extra fields in received:
266 location,
267 previous_login,
268"#
269 );
270 }
271
272 #[test]
273 fn it_should_error_for_nested_contains_via_array_on_differences() {
274 let left = json!({ "name": "John", "comments": [
275 {
276 "text": "Hello",
277 "author": {
278 "name": "🦊",
279 "age": 25
280 }
281 },
282 {
283 "text": "Goodbye",
284 "author": {
285 "name": "John",
286 "age": 30
287 }
288 }
289 ]});
290
291 let right = json!(expect::object().contains(
292 json!({ "comments": expect::array().contains([
293 json!({
294 "text": "Hello",
295 "author": expect::object().contains(
296 json!({
297 "name": "Jane",
298 })
299 )
300 }),
301 ])})
302 ));
303
304 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
305 assert_eq!(
306 output,
307 r#"Json array at root.comments does not contain expected value:
308 expected array to contain {
309 "author": expect::object(),
310 "text": "Hello"
311 }, but it was not found.
312 received [
313 {
314 "author": {
315 "age": 25,
316 "name": "🦊"
317 },
318 "text": "Hello"
319 },
320 {
321 "author": {
322 "age": 30,
323 "name": "John"
324 },
325 "text": "Goodbye"
326 }
327 ]"#
328 );
329 }
330
331 #[test]
332 fn it_should_error_for_nested_contains_via_object_with_inner_contains_error() {
333 let left = json!({
334 "name": "John",
335 "comment": {
336 "text": "Hello",
337 "author": {
338 "name": "🦊",
339 "age": 25
340 }
341 },
342 });
343
344 let right = json!(expect::object().contains(json!({ "comment":
345 expect::object().contains(
346 json!({
347 "text": "Hello",
348 "author": expect::object().contains(
349 json!({
350 "name": "Jane",
351 })
352 )
353 })
354 )
355 })));
356
357 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
358 assert_eq!(
359 output,
360 r#"Json strings at root.comment.author.name are not equal:
361 expected "Jane"
362 received "🦊""#
363 );
364 }
365
366 #[test]
369 fn it_should_error_for_nested_contains_via_different_object_with_inner_contains_error() {
370 let left = json!({
371 "name": "John",
372 "comment": {
373 "text": "Hello",
374 "author": {
375 "name": "Jane",
376 "age": 25
377 }
378 },
379 });
380
381 let right = json!(expect::object().contains(json!({ "comment":
382 expect::object().contains(
383 json!({
384 "text": "Hello",
385 "author": expect::object().contains(
386 json!({
387 "something_else": "🦊",
388 })
389 )
390 })
391 )
392 })));
393
394 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
395 assert_eq!(
396 output,
397 r#"Json object at root.comment.author is missing key for object:
398 expected field 'something_else',
399 but it was not found"#
400 );
401 }
402}
403
404#[cfg(test)]
405mod test_propagated_contains {
406 use crate::expect;
407 use crate::expect_json_eq;
408 use pretty_assertions::assert_eq;
409 use serde_json::json;
410
411 #[test]
412 fn it_should_be_equal_for_identical_objects() {
413 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
414 let right = json!(
415 expect::object()
416 .propagated_contains(json!({ "name": "John", "age": 30, "scores": [1, 2, 3] }))
417 );
418
419 let output = expect_json_eq(&left, &right);
420 assert!(output.is_ok());
421 }
422
423 #[test]
424 fn it_should_be_equal_for_reversed_identical_objects() {
425 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
426 let right = json!(
427 expect::object()
428 .propagated_contains(json!({ "scores": [1, 2, 3], "age": 30, "name": "John" }))
429 );
430
431 let output = expect_json_eq(&left, &right);
432 assert!(output.is_ok());
433 }
434
435 #[test]
436 fn it_should_be_equal_for_partial_contains() {
437 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
438 let right =
439 json!(expect::object().propagated_contains(json!({ "name": "John", "age": 30 })));
440
441 let output = expect_json_eq(&left, &right);
442 assert!(output.is_ok());
443 }
444
445 #[test]
446 fn it_should_be_equal_for_nested_contains() {
447 let left = json!({ "name": "John", "comments": [
448 {
449 "text": "Hello",
450 "author": {
451 "name": "Jane",
452 "age": 25
453 }
454 },
455 {
456 "text": "Goodbye",
457 "author": {
458 "name": "John",
459 "age": 30
460 }
461 }
462 ]});
463
464 let right = json!(expect::object().propagated_contains(
465 json!({ "comments": expect::array().contains([
466 json!({
467 "text": "Hello",
468 "author": expect::object().contains(
469 json!({
470 "name": "Jane",
471 })
472 )
473 }),
474 ])})
475 ));
476
477 let output = expect_json_eq(&left, &right);
478 assert!(output.is_ok(), "{}", output.unwrap_err().to_string());
479 }
480
481 #[test]
482 fn it_should_accept_nested_object_with_missing_fields() {
483 let left = json!({
484 "name": "John",
485 "id": "abd123",
486 "meta": {
487 "location": "uk",
488 "previous_login": "sometime last thursday",
489 }
490 });
491
492 let right = json!(expect::object().propagated_contains(json!({
493 "name": "John",
494 "meta": {},
495 })));
496
497 let result = expect_json_eq(&left, &right);
498 assert!(result.is_ok(), "{result:#?}");
499 }
500
501 #[test]
502 fn it_should_error_if_inner_contains_fails_to_contain_fields() {
503 let left = json!({
504 "name": "John",
505 "id": "abd123",
506 "meta": {
507 "inner": {
508 "location": "uk",
509 "previous_login": "sometime last thursday",
510 }
511 }
512 });
513
514 let right = json!(expect::object().propagated_contains(json!({
515 "name": "John",
516 "meta": expect::object().contains(json!({
517 "inner": {}
518 })),
519 })));
520
521 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
522 assert_eq!(
523 output,
524 r#"Json object at root.meta.inner has many extra fields over expected:
525 expected {}
526 received {
527 "location": "uk",
528 "previous_login": "sometime last thursday"
529 }
530
531 extra fields in received:
532 location,
533 previous_login,
534"#
535 );
536 }
537
538 #[test]
539 fn it_should_error_for_same_fields_but_different_values() {
540 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
541 let right = json!(
542 expect::object()
543 .propagated_contains(json!({ "name": "Joe", "age": 31, "scores": [4, 5, 6] }))
544 );
545
546 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
547 assert_eq!(
548 output,
549 r#"Json integers at root.age are not equal:
550 expected 31
551 received 30"#
552 );
553 }
554
555 #[test]
556 fn it_should_be_ok_for_empty_contains() {
557 let left = json!({ "name": "John", "age": 30, "scores": [1, 2, 3] });
558 let right = json!(expect::object().propagated_contains(json!({})));
559
560 let output = expect_json_eq(&left, &right);
561 assert!(output.is_ok());
562 }
563
564 #[test]
565 fn it_should_be_ok_for_empty_on_empty_object() {
566 let left = json!({});
567 let right = json!(expect::object().propagated_contains(json!({})));
568
569 let output = expect_json_eq(&left, &right);
570 assert!(output.is_ok());
571 }
572
573 #[test]
574 fn it_should_error_if_used_against_the_wrong_type() {
575 let left = json!("🦊");
576 let right = json!(expect::object().propagated_contains(json!({})));
577
578 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
579 assert_eq!(
580 output,
581 r#"Json expect::object() at root, received wrong type:
582 expected object
583 received string "🦊""#
584 );
585 }
586
587 #[test]
588 fn it_should_error_for_nested_contains_via_object_with_inner_contains_error() {
589 let left = json!({
590 "name": "John",
591 "comment": {
592 "text": "Hello",
593 "author": {
594 "name": "🦊",
595 "age": 25
596 }
597 },
598 });
599
600 let right = json!(expect::object().propagated_contains(json!({ "comment":
601 expect::object().propagated_contains(
602 json!({
603 "text": "Hello",
604 "author": expect::object().contains(
605 json!({
606 "name": "Jane",
607 })
608 )
609 })
610 )
611 })));
612
613 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
614 assert_eq!(
615 output,
616 r#"Json strings at root.comment.author.name are not equal:
617 expected "Jane"
618 received "🦊""#
619 );
620 }
621
622 #[test]
625 fn it_should_error_for_nested_contains_via_different_object_with_inner_contains_error() {
626 let left = json!({
627 "name": "John",
628 "comment": {
629 "text": "Hello",
630 "author": {
631 "name": "Jane",
632 "age": 25
633 }
634 },
635 });
636
637 let right = json!(expect::object().propagated_contains(json!({ "comment":
638 expect::object().propagated_contains(
639 json!({
640 "text": "Hello",
641 "author": expect::object().contains(
642 json!({
643 "something_else": "🦊",
644 })
645 )
646 })
647 )
648 })));
649
650 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
651 assert_eq!(
652 output,
653 r#"Json object at root.comment.author is missing key for object:
654 expected field 'something_else',
655 but it was not found"#
656 );
657 }
658}
659
660#[cfg(test)]
661mod test_empty {
662 use crate::expect;
663 use crate::expect_json_eq;
664 use pretty_assertions::assert_eq;
665 use serde_json::json;
666
667 #[test]
668 fn it_should_pass_when_object_is_empty() {
669 let left = json!({});
670 let right = json!(expect::object().empty());
671
672 let output = expect_json_eq(&left, &right);
673 assert!(output.is_ok(), "assertion error: {output:#?}");
674 }
675
676 #[test]
677 fn it_should_fail_when_object_is_not_empty() {
678 let left = json!({ "foo": "bar" });
679 let right = json!(expect::object().empty());
680
681 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
682 assert_eq!(
683 output,
684 r#"Json expect::object() error at root:
685 expected empty object
686 received {
687 "foo": "bar"
688 }"#
689 );
690 }
691}
692
693#[cfg(test)]
694mod test_not_empty {
695 use crate::expect;
696 use crate::expect_json_eq;
697 use pretty_assertions::assert_eq;
698 use serde_json::json;
699
700 #[test]
701 fn it_should_pass_when_object_is_not_empty() {
702 let left = json!({ "foo": "bar" });
703 let right = json!(expect::object().not_empty());
704
705 let output = expect_json_eq(&left, &right);
706 assert!(output.is_ok(), "assertion error: {output:#?}");
707 }
708
709 #[test]
710 fn it_should_fail_when_object_is_empty() {
711 let left = json!({});
712 let right = json!(expect::object().not_empty());
713
714 let output = expect_json_eq(&left, &right).unwrap_err().to_string();
715 assert_eq!(
716 output,
717 r#"Json expect::object() error at root:
718 expected non-empty object
719 received {}"#
720 );
721 }
722}