use crate::json_path::JsonPath;
use serde_json::Value;
pub trait ValueVisitor {
type Output: Default;
fn visit_null(
&mut self,
path: &JsonPath,
old_value: Option<&Value>,
new_value: Option<&Value>,
) -> Self::Output;
fn visit_bool(
&mut self,
path: &JsonPath,
old_value: Option<&bool>,
new_value: Option<&bool>,
) -> Self::Output;
fn visit_number(
&mut self,
path: &JsonPath,
old_value: Option<&Value>,
new_value: Option<&Value>,
) -> Self::Output;
fn visit_string(
&mut self,
path: &JsonPath,
old_value: Option<&String>,
new_value: Option<&String>,
) -> Self::Output;
fn visit_array(
&mut self,
path: &JsonPath,
old_value: Option<&Vec<Value>>,
new_value: Option<&Vec<Value>>,
) -> Self::Output;
fn visit_object(
&mut self,
path: &JsonPath,
old_value: Option<&serde_json::Map<String, Value>>,
new_value: Option<&serde_json::Map<String, Value>>,
) -> Self::Output;
#[allow(unused)]
fn visit_equal(&mut self, _path: &JsonPath, _value: &Value) -> Self::Output {
Self::Output::default()
}
}
pub fn traverse<V>(
old: Option<&Value>,
new: Option<&Value>,
path: &JsonPath,
visitor: &mut V,
) -> V::Output
where
V: ValueVisitor + ValueVisitorExt,
{
match (old, new) {
(None, Some(new)) => {
match new {
Value::Null => visitor.visit_null(path, None, Some(new)),
Value::Bool(b) => visitor.visit_bool(path, None, Some(b)),
Value::Number(_n) => visitor.visit_number(path, None, Some(new)),
Value::String(s) => visitor.visit_string(path, None, Some(s)),
Value::Array(a) => visitor.visit_array(path, None, Some(a)),
Value::Object(o) => visitor.visit_object(path, None, Some(o)),
}
}
(Some(old), None) => {
match old {
Value::Null => visitor.visit_null(path, Some(old), None),
Value::Bool(b) => visitor.visit_bool(path, Some(b), None),
Value::Number(_n) => visitor.visit_number(path, Some(old), None),
Value::String(s) => visitor.visit_string(path, Some(s), None),
Value::Array(a) => visitor.visit_array(path, Some(a), None),
Value::Object(o) => visitor.visit_object(path, Some(o), None),
}
}
(Some(old), Some(new)) => {
if old == new {
visitor.visit_equal(path, new)
} else {
match (old, new) {
(Value::Null, Value::Null) => visitor.visit_null(path, Some(old), Some(new)),
(Value::Bool(_), Value::Bool(_)) => {
visitor.visit_bool(path, old.as_bool().as_ref(), new.as_bool().as_ref())
}
(Value::Number(_), Value::Number(_)) => {
visitor.visit_number(path, Some(old), Some(new))
}
(Value::String(_), Value::String(_)) => visitor.visit_string(
path,
old.as_str().map(|s| s.to_string()).as_ref(),
new.as_str().map(|s| s.to_string()).as_ref(),
),
(Value::Array(_), Value::Array(_)) => {
visitor.visit_array(path, old.as_array(), new.as_array())
}
(Value::Object(_), Value::Object(_)) => {
visitor.visit_object(path, old.as_object(), new.as_object())
}
(_, _) => {
visitor.visit_modified(path, Some(old), Some(new))
}
}
}
}
(None, None) => {
<V as ValueVisitor>::Output::default()
}
}
}
pub trait ValueVisitorExt: ValueVisitor {
fn visit_modified(
&mut self,
path: &JsonPath,
old_value: Option<&Value>,
new_value: Option<&Value>,
) -> Self::Output
where
Self: Sized,
{
match (old_value, new_value) {
(Some(old), Some(new)) => traverse(Some(old), Some(new), path, self),
(Some(old), None) => {
self.visit_object(path, old.as_object(), None)
}
(None, Some(new)) => {
self.visit_object(path, None, new.as_object())
}
(None, None) => {
Self::Output::default()
}
}
}
}