use crate::Patch;
use jsonptr::Pointer;
use serde_json::{Map, Value};
fn diff_impl(left: &Value, right: &Value, pointer: &mut Pointer, patch: &mut super::Patch) {
match (left, right) {
(Value::Object(ref left_obj), Value::Object(ref right_obj)) => {
diff_object(left_obj, right_obj, pointer, patch);
}
(Value::Array(ref left_array), Value::Array(ref ref_array)) => {
diff_array(left_array, ref_array, pointer, patch);
}
(_, _) if left == right => {
}
(_, _) => {
patch
.0
.push(super::PatchOperation::Replace(super::ReplaceOperation {
path: pointer.clone(),
value: right.clone(),
}));
}
}
}
fn diff_array(left: &[Value], right: &[Value], pointer: &mut Pointer, patch: &mut Patch) {
let len = left.len().max(right.len());
let mut shift = 0usize;
for idx in 0..len {
pointer.push_back((idx - shift).into());
match (left.get(idx), right.get(idx)) {
(Some(left), Some(right)) => {
diff_impl(left, right, pointer, patch);
}
(Some(_left), None) => {
shift += 1;
patch
.0
.push(super::PatchOperation::Remove(super::RemoveOperation {
path: pointer.clone(),
}));
}
(None, Some(right)) => {
patch
.0
.push(super::PatchOperation::Add(super::AddOperation {
path: pointer.clone(),
value: right.clone(),
}));
}
(None, None) => {
unreachable!()
}
}
pointer.pop_back();
}
}
fn diff_object(
left: &Map<String, Value>,
right: &Map<String, Value>,
pointer: &mut Pointer,
patch: &mut Patch,
) {
for (key, right_value) in right {
pointer.push_back(key.into());
match left.get(key) {
Some(left_value) => {
diff_impl(left_value, right_value, pointer, patch);
}
None => {
patch
.0
.push(super::PatchOperation::Add(super::AddOperation {
path: pointer.clone(),
value: right_value.clone(),
}));
}
}
pointer.pop_back();
}
for key in left.keys() {
if !right.contains_key(key) {
pointer.push_back(key.into());
patch
.0
.push(super::PatchOperation::Remove(super::RemoveOperation {
path: pointer.clone(),
}));
pointer.pop_back();
}
}
}
pub fn diff(left: &Value, right: &Value) -> super::Patch {
let mut patch = super::Patch::default();
let mut path = Pointer::root();
diff_impl(left, right, &mut path, &mut patch);
patch
}
#[cfg(test)]
mod tests {
use serde_json::{json, Value};
#[test]
pub fn replace_all() {
let mut left = json!({"title": "Hello!"});
let patch = super::diff(&left, &Value::Null);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": null },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
}
#[test]
pub fn diff_empty_key() {
let mut left = json!({"title": "Something", "": "Hello!"});
let right = json!({"title": "Something", "": "Bye!"});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "/", "value": "Bye!" },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn add_all() {
let right = json!({"title": "Hello!"});
let patch = super::diff(&Value::Null, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": { "title": "Hello!" } },
]))
.unwrap()
);
let mut left = Value::Null;
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn remove_all() {
let mut left = json!(["hello", "bye"]);
let right = json!([]);
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/0" },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn remove_tail() {
let mut left = json!(["hello", "bye", "hi"]);
let right = json!(["hello"]);
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/1" },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn add_tail() {
let mut left = json!(["hello"]);
let right = json!(["hello", "bye", "hi"]);
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "add", "path": "/1", "value": "bye" },
{ "op": "add", "path": "/2", "value": "hi" }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn replace_object() {
let mut left = json!(["hello", "bye"]);
let right = json!({"hello": "bye"});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": {"hello": "bye"} }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
fn escape_json_keys() {
let mut left = json!({
"/slashed/path/with/~": 1
});
let right = json!({
"/slashed/path/with/~": 2,
});
let patch = super::diff(&left, &right);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn replace_object_array() {
let mut left = json!({ "style": { "ref": {"name": "name"} } });
let right = json!({ "style": [{ "ref": {"hello": "hello"} }]});
let patch = crate::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "/style", "value": [{ "ref": {"hello": "hello"} }] },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn replace_array_object() {
let mut left = json!({ "style": [{ "ref": {"hello": "hello"} }]});
let right = json!({ "style": { "ref": {"name": "name"} } });
let patch = crate::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "/style", "value": { "ref": {"name": "name"} } },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn remove_keys() {
let mut left = json!({"first": 1, "second": 2, "third": 3});
let right = json!({"first": 1, "second": 2});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "remove", "path": "/third" }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn add_keys() {
let mut left = json!({"first": 1, "second": 2});
let right = json!({"first": 1, "second": 2, "third": 3});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "add", "path": "/third", "value": 3 }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
}