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