#![deny(warnings)]
#![warn(missing_docs)]
#[macro_use]
extern crate serde_derive;
#[cfg_attr(test, macro_use)]
extern crate serde_json;
use serde_json::{Map, Value};
use std::error::Error;
use std::{fmt, mem};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Patch(pub Vec<PatchOperation>);
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AddOperation {
pub path: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RemoveOperation {
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct ReplaceOperation {
pub path: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct MoveOperation {
pub from: String,
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct CopyOperation {
pub from: String,
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct TestOperation {
pub path: String,
pub 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),
}
#[derive(Debug)]
pub enum PatchError {
InvalidPointer,
TestFailed,
}
impl Error for PatchError {
fn description(&self) -> &str {
match *self {
PatchError::InvalidPointer => "invalid pointer",
PatchError::TestFailed => "test failed",
}
}
}
impl fmt::Display for PatchError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.description().fmt(fmt)
}
}
fn parse_index(str: &str, len: usize) -> Result<usize, PatchError> {
if str.starts_with('0') && str.len() != 1 {
return Err(PatchError::InvalidPointer);
}
match str.parse::<usize>() {
Ok(idx) if idx < len => Ok(idx),
_ => Err(PatchError::InvalidPointer),
}
}
fn split_pointer(pointer: &str) -> Result<(&str, String), PatchError> {
pointer
.rfind('/')
.ok_or(PatchError::InvalidPointer)
.map(|idx| {
(
&pointer[0..idx],
pointer[idx + 1..].replace("~1", "/").replace("~0", "~"),
)
})
}
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(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),
};
match *patch {
PatchOperation::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
})
}
PatchOperation::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
})
}
PatchOperation::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
})
}
PatchOperation::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
})
}
PatchOperation::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
})
}
PatchOperation::Test(ref op) => {
test(doc, op.path.as_str(), &op.value)?;
apply_patches(doc, tail)
}
}
}
pub fn patch_unsafe(doc: &mut Value, patch: &Patch) -> Result<(), PatchError> {
for op in &patch.0 {
match *op {
PatchOperation::Add(ref op) => {
add(doc, op.path.as_str(), op.value.clone())?;
}
PatchOperation::Remove(ref op) => {
remove(doc, op.path.as_str(), false)?;
}
PatchOperation::Replace(ref op) => {
replace(doc, op.path.as_str(), op.value.clone())?;
}
PatchOperation::Move(ref op) => {
mov(doc, op.from.as_str(), op.path.as_str(), false)?;
}
PatchOperation::Copy(ref op) => {
copy(doc, op.from.as_str(), op.path.as_str())?;
}
PatchOperation::Test(ref op) => {
test(doc, op.path.as_str(), &op.value)?;
}
};
}
Ok(())
}
pub fn merge(doc: &mut Value, patch: &Value) {
if !patch.is_object() {
*doc = patch.clone();
return;
}
if !doc.is_object() {
*doc = Value::Object(Map::new());
}
let map = doc.as_object_mut().unwrap();
for (key, value) in patch.as_object().unwrap() {
if value.is_null() {
map.remove(key.as_str());
} else {
merge(map.entry(key.as_str()).or_insert(Value::Null), value);
}
}
}
#[cfg(feature = "diff")]
mod diff;
#[cfg(feature = "diff")]
pub use self::diff::diff;
#[cfg(test)]
mod tests;