#![feature(test)]
#![deny(warnings)]
#![warn(missing_docs)]
#[macro_use]
extern crate serde_derive;
#[cfg_attr(test, macro_use)]
extern crate serde_json;
use serde_json::Value;
use std::mem;
use util::{parse_index, split_pointer};
pub use util::PatchError;
mod util;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Patch(Vec<PatchOperation>);
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AddOperation {
path: String,
value: Value
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RemoveOperation {
path: String
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct ReplaceOperation {
path: String,
value: Value
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct MoveOperation {
from: String,
path: String
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct CopyOperation {
from: String,
path: String
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct TestOperation {
path: String,
value: Value
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "op")]
#[serde(rename_all = "lowercase")]
pub enum PatchOperation {
Add(AddOperation),
Remove(RemoveOperation),
Replace(ReplaceOperation),
Move(MoveOperation),
Copy(CopyOperation),
Test(TestOperation)
}
fn add(doc: &mut Value, path: &str, value: Value) -> Result<Option<Value>, PatchError> {
if path == "" {
return Ok(Some(mem::replace(doc, value)));
}
let (parent, last) = split_pointer(path)?;
let parent = doc.pointer_mut(parent)
.ok_or(PatchError::InvalidPointer)?;
match *parent {
Value::Object(ref mut obj) => {
Ok(obj.insert(String::from(last), value))
}
Value::Array(ref mut arr) if last == "-" => {
arr.push(value);
Ok(None)
}
Value::Array(ref mut arr) => {
let idx = parse_index(last.as_str(), arr.len() + 1)?;
arr.insert(idx, value);
Ok(None)
}
_ => Err(PatchError::InvalidPointer)
}
}
fn remove(doc: &mut Value, path: &str, allow_last: bool) -> Result<Value, PatchError> {
let (parent, last) = split_pointer(path)?;
let parent = doc.pointer_mut(parent)
.ok_or(PatchError::InvalidPointer)?;
match *parent {
Value::Object(ref mut obj) => {
match obj.remove(last.as_str()) {
None => Err(PatchError::InvalidPointer),
Some(val) => Ok(val)
}
}
Value::Array(ref mut arr) if allow_last && last == "-" => {
Ok(arr.pop().unwrap())
}
Value::Array(ref mut arr) => {
let idx = parse_index(last.as_str(), arr.len())?;
Ok(arr.remove(idx))
}
_ => Err(PatchError::InvalidPointer)
}
}
fn replace(doc: &mut Value, path: &str, value: Value) -> Result<Value, PatchError> {
let target = doc
.pointer_mut(path)
.ok_or(PatchError::InvalidPointer)?;
Ok(mem::replace(target, value))
}
fn mov(doc: &mut Value, from: &str, path: &str, allow_last: bool) -> Result<Option<Value>, PatchError> {
if path.starts_with(from) && path[from.len()..].starts_with('/') {
return Err(PatchError::InvalidPointer);
}
let val = remove(doc, from, allow_last)?;
add(doc, path, val)
}
fn copy(doc: &mut Value, from: &str, path: &str) -> Result<Option<Value>, PatchError> {
let source = doc
.pointer(from)
.ok_or(PatchError::InvalidPointer)?
.clone();
add(doc, path, source)
}
fn test(doc: &Value, path: &str, expected: &Value) -> Result<(), PatchError> {
let target = doc
.pointer(path)
.ok_or(PatchError::InvalidPointer)?;
if *target == *expected {
Ok(())
} else {
Err(PatchError::TestFailed)
}
}
pub fn from_value(value: Value) -> Result<Patch, serde_json::Error> {
let patch = serde_json::from_value::<Vec<PatchOperation>>(value)?;
Ok(Patch(patch))
}
pub fn patch(doc: &mut Value, patch: &Patch) -> Result<(), PatchError> {
apply_patches(doc, &patch.0)
}
fn apply_patches(doc: &mut Value, patches: &[PatchOperation]) -> Result<(), PatchError> {
let (patch, tail) = match patches.split_first() {
None => return Ok(()),
Some((patch, tail)) => (patch, tail)
};
use PatchOperation::*;
match *patch {
Add(ref op) => {
let prev = add(doc, op.path.as_str(), op.value.clone())?;
apply_patches(doc, tail).map_err(move |e| {
match prev {
None => remove(doc, op.path.as_str(), true).unwrap(),
Some(v) => add(doc, op.path.as_str(), v).unwrap().unwrap()
};
e
})
}
Remove(ref op) => {
let prev = remove(doc, op.path.as_str(), false)?;
apply_patches(doc, tail).map_err(move |e| {
assert!(add(doc, op.path.as_str(), prev).unwrap().is_none());
e
})
}
Replace(ref op) => {
let prev = replace(doc, op.path.as_str(), op.value.clone())?;
apply_patches(doc, tail).map_err(move |e| {
replace(doc, op.path.as_str(), prev).unwrap();
e
})
}
Move(ref op) => {
let prev = mov(doc, op.from.as_str(), op.path.as_str(), false)?;
apply_patches(doc, tail).map_err(move |e| {
mov(doc, op.path.as_str(), op.from.as_str(), true).unwrap();
if let Some(prev) = prev {
assert!(add(doc, op.path.as_str(), prev).unwrap().is_none());
}
e
})
}
Copy(ref op) => {
let prev = copy(doc, op.from.as_str(), op.path.as_str())?;
apply_patches(doc, tail).map_err(move |e| {
match prev {
None => remove(doc, op.path.as_str(), true).unwrap(),
Some(v) => add(doc, op.path.as_str(), v).unwrap().unwrap()
};
e
})
}
Test(ref op) => {
test(doc, op.path.as_str(), &op.value)?;
apply_patches(doc, tail)
}
}
}
pub unsafe fn patch_unsafe(doc: &mut Value, patch: &Patch) -> Result<(), PatchError> {
use PatchOperation::*;
for op in &patch.0 {
match *op {
Add(ref op) => { add(doc, op.path.as_str(), op.value.clone())?; }
Remove(ref op) => { remove(doc, op.path.as_str(), false)?; }
Replace(ref op) => { replace(doc, op.path.as_str(), op.value.clone())?; }
Move(ref op) => { mov(doc, op.from.as_str(), op.path.as_str(), false)?; }
Copy(ref op) => { copy(doc, op.from.as_str(), op.path.as_str())?; }
Test(ref op) => { test(doc, op.path.as_str(), &op.value)?; }
};
}
Ok(())
}
#[cfg(test)]
mod tests;