assert_json/
lib.rs

1//! A easy and declarative way to test JSON input in Rust.
2//!
3//! [assert_json!] is a Rust macro heavily inspired by [serde_json::json!] macro.
4//! Instead of creating a JSON value from a JSON literal, [assert_json!] makes sure
5//! the JSON input conforms to the validation rules specified.
6//!
7//! [assert_json!] also output beautiful error message when a validation error occurs.
8//!
9//! ```
10//! # use assert_json::assert_json;
11//! # use assert_json::validators;
12//! #
13//! #[test]
14//! fn test_json_ok() {
15//!     let json = r#"
16//!         {
17//!             "status": "success",
18//!             "result": {
19//!                 "id": 5,
20//!                 "name": "charlesvdv"
21//!             }
22//!         }
23//!     "#;
24//!
25//!     let name = "charlesvdv";
26//!
27//!     assert_json!(json, {
28//!             "status": "success",
29//!             "result": {
30//!                 "id": validators::u64(|&v| if v > 0 { Ok(())} else { Err(String::from("id should be greater than 0")) }),
31//!                 "name": name,
32//!             }
33//!         }
34//!     );
35//! }
36//! ```
37
38/// A JSON-value. Used by the [Validator] trait.
39pub type Value = serde_json::Value;
40
41fn get_value_type_id(val: &Value) -> String {
42    match val {
43        serde_json::Value::Null => String::from("null"),
44        serde_json::Value::Bool(_) => String::from("bool"),
45        serde_json::Value::Number(_) => String::from("number"),
46        serde_json::Value::String(_) => String::from("string"),
47        serde_json::Value::Array(_) => String::from("array"),
48        serde_json::Value::Object(_) => String::from("object"),
49    }
50}
51
52/// Validation error
53#[derive(thiserror::Error, Debug, PartialEq)]
54pub enum Error<'a> {
55    #[error("Invalid type. Expected {} but got {}.", .1, get_value_type_id(.0))]
56    InvalidType(&'a Value, String),
57    #[error("Invalid value. Expected {1} but got {0}.")]
58    InvalidValue(&'a Value, String),
59    #[error("Missing key '{1}' in object")]
60    MissingObjectKey(&'a Value, String),
61    #[error("Key '{1}' is not expected in object")]
62    UnexpectedObjectKey(&'a Value, String),
63}
64
65impl<'a> Error<'a> {
66    fn location(&self) -> &'a Value {
67        match self {
68            Error::InvalidType(loc, _) => loc,
69            Error::InvalidValue(loc, _) => loc,
70            Error::MissingObjectKey(loc, _) => loc,
71            Error::UnexpectedObjectKey(loc, _) => loc,
72        }
73    }
74}
75
76/// Abstract the validation action for [assert_json!] macro.
77///
78/// Any custom validation rule can be easily use in the macro
79/// by implementing the [Validator::validate] method.
80///
81/// ```
82/// use assert_json::{assert_json, Error, Validator, Value};
83///
84/// fn optional_string(expected: Option<String>) -> impl Validator {
85///     OptionalStringValidator { expected }
86/// }
87///
88/// /// Matches a null JSON value if expected is None, else check if the strings
89/// /// are equals
90/// struct OptionalStringValidator {
91///     expected: Option<String>,
92/// }
93///
94/// impl Validator for OptionalStringValidator {
95///     fn validate<'a>(&self, value: &'a Value) -> Result<(), Error<'a>> {
96///         if let Some(expected_str) = &self.expected {
97///             let string_value = value
98///                 .as_str()
99///                 .ok_or_else(|| Error::InvalidType(value, String::from("string")))?;
100///
101///             if expected_str == string_value {
102///                 Ok(())
103///             } else {
104///                 Err(Error::InvalidValue(value, expected_str.clone()))
105///             }
106///         } else {
107///             value.as_null()
108///                 .ok_or_else(|| Error::InvalidType(value, String::from("null")))
109///         }
110///     }
111/// }
112///
113/// let json = r#"
114///     {
115///         "key": "value",
116///         "none": null
117///     }
118/// "#;
119/// assert_json!(json, {
120///     "key": optional_string(Some(String::from("value"))),
121///     "none": optional_string(None),
122/// });
123/// ```
124pub trait Validator {
125    fn validate<'a>(&self, value: &'a Value) -> Result<(), Error<'a>>;
126
127    fn and<T>(self, validator: T) -> And<Self, T>
128    where
129        Self: Sized,
130        T: Validator,
131    {
132        And {
133            first: self,
134            second: validator,
135        }
136    }
137}
138
139#[doc(hidden)]
140pub struct And<T, U> {
141    first: T,
142    second: U,
143}
144
145impl<T, U> Validator for And<T, U>
146where
147    T: Validator,
148    U: Validator,
149{
150    fn validate<'a>(&self, value: &'a Value) -> Result<(), Error<'a>> {
151        self.first.validate(value).and(self.second.validate(value))
152    }
153}
154
155/// Custom validators for different JSON types
156pub mod validators;
157
158#[macro_use]
159mod macros;
160#[doc(hidden)]
161pub mod macros_utils;