googletest_json_serde/matchers/
json_matcher.rs

1//! Utility matchers and macros for concise JSON assertions using googletest.
2
3use crate::matchers::__internal_unstable_do_not_depend_on_these::JsonPredicateMatcher;
4
5/// Matches JSON null values.
6pub fn is_null() -> JsonPredicateMatcher {
7    JsonPredicateMatcher::new(|v| v.is_null(), "JSON null", "which is not JSON null")
8}
9
10/// Matches any JSON value except null.
11pub fn any_value() -> JsonPredicateMatcher {
12    JsonPredicateMatcher::new(
13        |v| !v.is_null(),
14        "any JSON value",
15        "which is not any JSON value",
16    )
17}
18
19/// Matches JSON string values.
20pub fn is_string() -> JsonPredicateMatcher {
21    JsonPredicateMatcher::new(
22        |v| v.is_string(),
23        "a JSON string",
24        "which is not a JSON string",
25    )
26}
27
28/// Matches JSON number values.
29pub fn is_number() -> JsonPredicateMatcher {
30    JsonPredicateMatcher::new(
31        |v| v.is_number(),
32        "a JSON number",
33        "which is not a JSON number",
34    )
35}
36
37/// Matches JSON boolean values.
38pub fn is_boolean() -> JsonPredicateMatcher {
39    JsonPredicateMatcher::new(
40        |v| v.is_boolean(),
41        "a JSON boolean",
42        "which is not a JSON boolean",
43    )
44}
45
46/// Matches JSON array values.
47pub fn is_array() -> JsonPredicateMatcher {
48    JsonPredicateMatcher::new(
49        |v| v.is_array(),
50        "a JSON array",
51        "which is not a JSON array",
52    )
53}
54
55/// Matches JSON object values.
56pub fn is_object() -> JsonPredicateMatcher {
57    JsonPredicateMatcher::new(
58        |v| v.is_object(),
59        "a JSON object",
60        "which is not a JSON object",
61    )
62}
63
64#[doc(hidden)]
65pub mod internal {
66    use googletest::description::Description;
67    use googletest::matcher::MatcherResult::{Match, NoMatch};
68    use googletest::matcher::{Matcher, MatcherBase, MatcherResult};
69    use serde_json::Value;
70
71    #[derive(MatcherBase)]
72    pub struct JsonPredicateMatcher {
73        predicate: fn(&Value) -> bool,
74        positive_description: &'static str,
75        negative_description: &'static str,
76    }
77    impl JsonMatcher for JsonPredicateMatcher {}
78
79    impl JsonPredicateMatcher {
80        pub fn new(
81            predicate: fn(&Value) -> bool,
82            positive_description: &'static str,
83            negative_description: &'static str,
84        ) -> Self {
85            Self {
86                predicate,
87                positive_description,
88                negative_description,
89            }
90        }
91    }
92    impl Matcher<&Value> for JsonPredicateMatcher {
93        fn matches(&self, actual: &Value) -> MatcherResult {
94            match (self.predicate)(actual) {
95                true => Match,
96                false => NoMatch,
97            }
98        }
99
100        fn describe(&self, matcher_result: MatcherResult) -> Description {
101            match matcher_result {
102                Match => self.positive_description.into(),
103                NoMatch => self.negative_description.into(),
104            }
105        }
106        fn explain_match(&self, actual: &Value) -> Description {
107            let kind = match actual {
108                Value::String(_) => "a JSON string",
109                Value::Number(_) => "a JSON number",
110                Value::Bool(_) => "a JSON boolean",
111                Value::Null => "a JSON null",
112                Value::Array(_) => "a JSON array",
113                Value::Object(_) => "a JSON object",
114            };
115            Description::new().text(format!("which is {kind}"))
116        }
117    }
118
119    /// Marker trait for JSON-aware matchers.
120    pub trait JsonMatcher: for<'a> Matcher<&'a Value> {}
121
122    /// Trait for converting into a boxed JSON matcher.
123    pub trait IntoJsonMatcher<T> {
124        fn into_json_matcher(self) -> Box<dyn for<'a> Matcher<&'a Value>>;
125    }
126
127    impl<J> IntoJsonMatcher<()> for J
128    where
129        J: JsonMatcher + 'static,
130    {
131        fn into_json_matcher(self) -> Box<dyn for<'a> Matcher<&'a Value>> {
132            Box::new(self)
133        }
134    }
135}