googletest_json_serde/matchers/
optional_matcher.rs

1/// Matches a JSON field that may be missing or null, or matches the given inner matcher.
2///
3/// Example:
4/// ```
5/// # use googletest::prelude::*;
6/// # use serde_json::json;
7/// # use googletest_json_serde::json;
8/// let value = json!({ "id": 42 });
9/// assert_that!(
10///     value,
11///     json::pat!({
12///         "id": eq(42),
13///         "nickname": json::optional!(eq("Bob"))
14///     })
15/// );
16/// ```
17#[macro_export]
18macro_rules! __json_optional {
19    ($inner:expr) => {{
20        $crate::matchers::__internal_unstable_do_not_depend_on_these::JsonOptionalMatcher::new(
21            $crate::matchers::__internal_unstable_do_not_depend_on_these::IntoJsonMatcher::into_json_matcher($inner),
22        )
23    }};
24}
25
26pub mod internal {
27    use crate::matchers::json_matcher::internal::JsonMatcher;
28    use googletest::description::Description;
29    use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
30    use serde_json::Value;
31
32    #[derive(MatcherBase)]
33    pub struct JsonOptionalMatcher {
34        inner: Box<dyn JsonMatcher>,
35    }
36
37    impl JsonOptionalMatcher {
38        pub fn new(inner: Box<dyn JsonMatcher>) -> Self {
39            Self { inner }
40        }
41    }
42
43    impl JsonMatcher for JsonOptionalMatcher {
44        fn allows_missing(&self) -> bool {
45            true
46        }
47    }
48
49    impl Matcher<&Value> for JsonOptionalMatcher {
50        fn matches(&self, actual: &Value) -> MatcherResult {
51            if actual.is_null() {
52                MatcherResult::Match
53            } else {
54                self.inner.matches(actual)
55            }
56        }
57
58        fn describe(&self, result: MatcherResult) -> Description {
59            match result {
60                MatcherResult::Match => "is null or matches inner matcher".into(),
61                MatcherResult::NoMatch => "neither null nor matches inner matcher".into(),
62            }
63        }
64
65        fn explain_match(&self, actual: &Value) -> Description {
66            if actual.is_null() {
67                Description::new().text("which is null")
68            } else {
69                self.inner.explain_match(actual)
70            }
71        }
72    }
73}