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;