use crate::api::Value;
use crate::array::Array;
use crate::error::JsonPatchError;
use crate::object::Object;
use crate::serde::{from_value, to_value};
use json_patch::jsonptr::PointerBuf;
use json_patch::{
AddOperation, CopyOperation, MoveOperation, Patch, PatchOperation, RemoveOperation,
ReplaceOperation, TestOperation, patch,
};
use serde_json::Value::Null;
fn add_operation(object: &Object) -> Option<PatchOperation> {
match (get_path(object, "path"), get_value(object)) {
(Some(p), Some(v)) => Some(PatchOperation::Add(AddOperation { path: p, value: v })),
(_, _) => None,
}
}
fn apply(source: &Value, json_patch: &Array) -> Result<Value, JsonPatchError> {
let mut value = to_value(source).unwrap_or(Null);
let p = array_to_patch(json_patch);
match patch(&mut value, &p) {
Ok(_) => Ok(from_value(&value).unwrap_or(Value::Null)),
Err(e) => Err(JsonPatchError {
source: Box::from(source.clone()),
patch: Box::from(json_patch.clone()),
error: e.to_string(),
}),
}
}
pub fn apply_array(source: &Array, json_patch: &Array) -> Result<Array, JsonPatchError> {
apply(&Value::Array(source.clone()), json_patch).and_then(|v| {
v.as_array().ok_or_else(|| JsonPatchError {
source: Box::from(Value::Array(source.clone())),
patch: Box::from(json_patch.clone()),
error: "the result is not an array".to_string(),
})
})
}
pub fn apply_object(source: &Object, json_patch: &Array) -> Result<Object, JsonPatchError> {
apply(&Value::Object(source.clone()), json_patch).and_then(|v| {
v.as_object().ok_or_else(|| JsonPatchError {
source: Box::from(Value::Object(source.clone())),
patch: Box::from(json_patch.clone()),
error: "the result is not an object".to_string(),
})
})
}
fn array_to_patch(array: &Array) -> Vec<PatchOperation> {
array
.iter()
.flat_map(|v| v.as_object().into_iter())
.flat_map(|o| object_to_path_operation(&o).into_iter())
.collect()
}
fn copy_operation(object: &Object) -> Option<PatchOperation> {
match (get_path(object, "from"), get_path(object, "path")) {
(Some(f), Some(p)) => Some(PatchOperation::Copy(CopyOperation { from: f, path: p })),
(_, _) => None,
}
}
fn diff(source: &Value, target: &Value) -> Array {
patch_to_array(&json_patch::diff(
&to_value(source).unwrap_or(Null),
&to_value(target).unwrap_or(Null),
))
}
pub fn diff_array(source: &Array, target: &Array) -> Array {
diff(&Value::Array(source.clone()), &Value::Array(target.clone()))
}
pub fn diff_object(source: &Object, target: &Object) -> Array {
diff(
&Value::Object(source.clone()),
&Value::Object(target.clone()),
)
}
fn get_path(object: &Object, field: &str) -> Option<PointerBuf> {
object.get_string(field).and_then(|v| v.parse().ok())
}
fn get_value(object: &Object) -> Option<serde_json::Value> {
object.get("value").and_then(to_value)
}
fn move_operation(object: &Object) -> Option<PatchOperation> {
match (get_path(object, "from"), get_path(object, "path")) {
(Some(f), Some(p)) => Some(PatchOperation::Move(MoveOperation { from: f, path: p })),
(_, _) => None,
}
}
fn object_to_path_operation(object: &Object) -> Option<PatchOperation> {
object.get_string("op").and_then(|s| match s.as_str() {
"add" => add_operation(object),
"copy" => copy_operation(object),
"move" => move_operation(object),
"remove" => remove_operation(object),
"replace" => replace_operation(object),
"test" => test_operation(object),
_ => None,
})
}
fn patch_operation_to_object(op: &PatchOperation) -> Object {
match op {
PatchOperation::Add(a) => Object::new()
.add_string("op", "add")
.add_string("path", a.path.as_str())
.add("value", &from_value(&a.value).unwrap_or(Value::Null)),
PatchOperation::Copy(c) => Object::new()
.add_string("op", "copy")
.add_string("from", c.from.as_str())
.add_string("path", c.path.as_str()),
PatchOperation::Move(m) => Object::new()
.add_string("op", "move")
.add_string("from", m.from.as_str())
.add_string("path", m.path.as_str()),
PatchOperation::Remove(r) => Object::new()
.add_string("op", "remove")
.add_string("path", r.path.as_str()),
PatchOperation::Replace(r) => Object::new()
.add_string("op", "replace")
.add_string("path", r.path.as_str())
.add("value", &from_value(&r.value).unwrap_or(Value::Null)),
PatchOperation::Test(t) => Object::new()
.add_string("op", "test")
.add_string("path", t.path.as_str())
.add("value", &from_value(&t.value).unwrap_or(Value::Null)),
}
}
fn patch_to_array(patch: &Patch) -> Array {
patch
.iter()
.map(patch_operation_to_object)
.fold(Array::new(), |a, o| a.add_object(&o))
}
fn remove_operation(object: &Object) -> Option<PatchOperation> {
get_path(object, "path").map(|p| PatchOperation::Remove(RemoveOperation { path: p }))
}
fn replace_operation(object: &Object) -> Option<PatchOperation> {
match (get_path(object, "path"), get_value(object)) {
(Some(p), Some(v)) => Some(PatchOperation::Replace(ReplaceOperation {
path: p,
value: v,
})),
(_, _) => None,
}
}
fn test_operation(object: &Object) -> Option<PatchOperation> {
match (get_path(object, "path"), get_value(object)) {
(Some(p), Some(v)) => Some(PatchOperation::Test(TestOperation { path: p, value: v })),
(_, _) => None,
}
}