use serde_json::Value;
struct PatchDiffer {
path: String,
patch: super::Patch,
shift: usize,
}
impl PatchDiffer {
fn new() -> Self {
Self {
path: "".to_string(),
patch: super::Patch(Vec::new()),
shift: 0,
}
}
}
impl<'a> treediff::Delegate<'a, treediff::value::Key, Value> for PatchDiffer {
fn push(&mut self, key: &treediff::value::Key) {
use std::fmt::Write;
self.path.push('/');
match *key {
treediff::value::Key::Index(idx) => write!(self.path, "{}", idx - self.shift).unwrap(),
treediff::value::Key::String(ref key) => append_path(&mut self.path, key),
}
}
fn pop(&mut self) {
let pos = self.path.rfind('/').unwrap_or(0);
self.path.truncate(pos);
self.shift = 0;
}
fn removed<'b>(&mut self, k: &'b treediff::value::Key, _v: &'a Value) {
let len = self.path.len();
self.push(k);
self.patch
.0
.push(super::PatchOperation::Remove(super::RemoveOperation {
path: self.path.clone(),
}));
if let treediff::value::Key::Index(_) = k {
self.shift += 1;
}
self.path.truncate(len);
}
fn added(&mut self, k: &treediff::value::Key, v: &Value) {
let len = self.path.len();
self.push(k);
self.patch
.0
.push(super::PatchOperation::Add(super::AddOperation {
path: self.path.clone(),
value: v.clone(),
}));
self.path.truncate(len);
}
fn modified(&mut self, _old: &'a Value, new: &'a Value) {
self.patch
.0
.push(super::PatchOperation::Replace(super::ReplaceOperation {
path: self.path.clone(),
value: new.clone(),
}));
}
}
fn append_path(path: &mut String, key: &str) {
path.reserve(key.len());
for ch in key.chars() {
if ch == '~' {
*path += "~0";
} else if ch == '/' {
*path += "~1";
} else {
path.push(ch);
}
}
}
pub fn diff(left: &Value, right: &Value) -> super::Patch {
let mut differ = PatchDiffer::new();
treediff::diff(left, right, &mut differ);
differ.patch
}
#[cfg(test)]
mod tests {
use serde_json::{json, Value};
#[test]
pub fn replace_all() {
let left = json!({"title": "Hello!"});
let p = super::diff(&left, &Value::Null);
assert_eq!(
p,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": null },
]))
.unwrap()
);
let mut left = json!({"title": "Hello!"});
crate::patch(&mut left, &p).unwrap();
}
#[test]
pub fn diff_empty_key() {
let left = json!({"title": "Something", "": "Hello!"});
let right = json!({"title": "Something", "": "Bye!"});
let p = super::diff(&left, &right);
assert_eq!(
p,
serde_json::from_value(json!([
{ "op": "replace", "path": "/", "value": "Bye!" },
]))
.unwrap()
);
let mut left_patched = json!({"title": "Something", "": "Hello!"});
crate::patch(&mut left_patched, &p).unwrap();
assert_eq!(left_patched, right);
}
#[test]
pub fn add_all() {
let right = json!({"title": "Hello!"});
let p = super::diff(&Value::Null, &right);
assert_eq!(
p,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": { "title": "Hello!" } },
]))
.unwrap()
);
}
#[test]
pub fn remove_all() {
let left = json!(["hello", "bye"]);
let right = json!([]);
let p = super::diff(&left, &right);
assert_eq!(
p,
serde_json::from_value(json!([
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/0" },
]))
.unwrap()
);
}
#[test]
pub fn remove_tail() {
let left = json!(["hello", "bye", "hi"]);
let right = json!(["hello"]);
let p = super::diff(&left, &right);
assert_eq!(
p,
serde_json::from_value(json!([
{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/1" },
]))
.unwrap()
);
}
#[test]
pub fn replace_object() {
let left = json!(["hello", "bye"]);
let right = json!({"hello": "bye"});
let p = super::diff(&left, &right);
assert_eq!(
p,
serde_json::from_value(json!([
{ "op": "add", "path": "/hello", "value": "bye" },
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/0" },
]))
.unwrap()
);
}
#[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);
}
}