use serde_json::{Map, Value};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum PatchError {
#[error("patch base is not a JSON object")]
BaseNotAnObject,
#[error("unset pointer {pointer:?} is not a well-formed JSON Pointer")]
PointerMalformed {
pointer: String,
},
#[error("unset pointer {pointer:?} does not resolve in the base")]
PointerMissing {
pointer: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Patch {
pub set: Map<String, Value>,
pub unset: Vec<String>,
}
#[must_use]
pub fn diff(base: &Map<String, Value>, next: &Map<String, Value>) -> Patch {
let mut unset = Vec::new();
let set = diff_objects(base, next, "", &mut unset);
Patch { set, unset }
}
fn diff_objects(
base: &Map<String, Value>,
next: &Map<String, Value>,
prefix: &str,
unset: &mut Vec<String>,
) -> Map<String, Value> {
let mut set = Map::new();
for (key, next_value) in next {
match (base.get(key), next_value) {
(Some(base_value), _) if base_value == next_value => {}
(Some(Value::Object(base_object)), Value::Object(next_object)) => {
let nested =
diff_objects(base_object, next_object, &child_pointer(prefix, key), unset);
if !nested.is_empty() {
set.insert(key.clone(), Value::Object(nested));
}
}
_ => {
set.insert(key.clone(), next_value.clone());
}
}
}
for key in base.keys().filter(|key| !next.contains_key(key.as_str())) {
unset.push(child_pointer(prefix, key));
}
set
}
pub fn apply(base: &Value, patch: &Patch) -> Result<Value, PatchError> {
let Value::Object(base_object) = base else {
return Err(PatchError::BaseNotAnObject);
};
let mut out = base_object.clone();
for pointer in &patch.unset {
remove_pointer(&mut out, pointer)?;
}
merge(&mut out, &patch.set);
Ok(Value::Object(out))
}
fn merge(target: &mut Map<String, Value>, set: &Map<String, Value>) {
for (key, value) in set {
match (target.get_mut(key), value) {
(Some(Value::Object(target_object)), Value::Object(nested)) => {
merge(target_object, nested);
}
_ => {
target.insert(key.clone(), value.clone());
}
}
}
}
fn remove_pointer(root: &mut Map<String, Value>, pointer: &str) -> Result<(), PatchError> {
let Some(body) = pointer.strip_prefix('/') else {
return Err(PatchError::PointerMalformed {
pointer: pointer.to_owned(),
});
};
let tokens: Vec<String> = body.split('/').map(unescape_token).collect();
let Some((leaf, parents)) = tokens.split_last() else {
return Err(PatchError::PointerMalformed {
pointer: pointer.to_owned(),
});
};
let mut cursor = root;
for parent in parents {
match cursor.get_mut(parent) {
Some(Value::Object(next)) => cursor = next,
_ => {
return Err(PatchError::PointerMissing {
pointer: pointer.to_owned(),
});
}
}
}
if cursor.remove(leaf).is_none() {
return Err(PatchError::PointerMissing {
pointer: pointer.to_owned(),
});
}
Ok(())
}
fn child_pointer(prefix: &str, key: &str) -> String {
format!("{prefix}/{}", escape_token(key))
}
fn escape_token(token: &str) -> String {
token.replace('~', "~0").replace('/', "~1")
}
fn unescape_token(token: &str) -> String {
token.replace("~1", "/").replace("~0", "~")
}