#![warn(missing_docs)]
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::borrow::Cow;
use thiserror::Error;
#[cfg(feature = "diff")]
mod diff;
#[cfg(feature = "diff")]
pub use self::diff::diff;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Patch(pub Vec<PatchOperation>);
impl std::ops::Deref for Patch {
type Target = [PatchOperation];
fn deref(&self) -> &[PatchOperation] {
&self.0
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct AddOperation {
pub path: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct RemoveOperation {
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct ReplaceOperation {
pub path: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct MoveOperation {
pub from: String,
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct CopyOperation {
pub from: String,
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct TestOperation {
pub path: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(tag = "op")]
#[serde(rename_all = "lowercase")]
pub enum PatchOperation {
Add(AddOperation),
Remove(RemoveOperation),
Replace(ReplaceOperation),
Move(MoveOperation),
Copy(CopyOperation),
Test(TestOperation),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PatchErrorKind {
#[error("value did not match")]
TestFailed,
#[error("\"from\" path is invalid")]
InvalidFromPointer,
#[error("path is invalid")]
InvalidPointer,
#[error("cannot move the value inside itself")]
CannotMoveInsideItself,
}
#[derive(Debug, Error)]
#[error("Operation '/{operation}' failed at path '{path}': {kind}")]
#[non_exhaustive]
pub struct PatchError {
pub operation: usize,
pub path: String,
pub kind: PatchErrorKind,
}
fn translate_error(kind: PatchErrorKind, operation: usize, path: &str) -> PatchError {
PatchError {
operation,
path: path.to_owned(),
kind,
}
}
fn unescape(s: &str) -> Cow<str> {
if s.contains('~') {
Cow::Owned(s.replace("~1", "/").replace("~0", "~"))
} else {
Cow::Borrowed(s)
}
}
fn parse_index(str: &str, len: usize) -> Result<usize, PatchErrorKind> {
if (str.starts_with('0') && str.len() != 1) || str.starts_with('+') {
return Err(PatchErrorKind::InvalidPointer);
}
match str.parse::<usize>() {
Ok(index) if index < len => Ok(index),
_ => Err(PatchErrorKind::InvalidPointer),
}
}
fn split_pointer(pointer: &str) -> Result<(&str, &str), PatchErrorKind> {
pointer
.rfind('/')
.ok_or(PatchErrorKind::InvalidPointer)
.map(|idx| (&pointer[0..idx], &pointer[idx + 1..]))
}
fn add(doc: &mut Value, path: &str, value: Value) -> Result<Option<Value>, PatchErrorKind> {
if path.is_empty() {
return Ok(Some(std::mem::replace(doc, value)));
}
let (parent, last_unescaped) = split_pointer(path)?;
let parent = doc
.pointer_mut(parent)
.ok_or(PatchErrorKind::InvalidPointer)?;
match *parent {
Value::Object(ref mut obj) => Ok(obj.insert(unescape(last_unescaped).into_owned(), value)),
Value::Array(ref mut arr) if last_unescaped == "-" => {
arr.push(value);
Ok(None)
}
Value::Array(ref mut arr) => {
let idx = parse_index(last_unescaped, arr.len() + 1)?;
arr.insert(idx, value);
Ok(None)
}
_ => Err(PatchErrorKind::InvalidPointer),
}
}
fn remove(doc: &mut Value, path: &str, allow_last: bool) -> Result<Value, PatchErrorKind> {
let (parent, last_unescaped) = split_pointer(path)?;
let parent = doc
.pointer_mut(parent)
.ok_or(PatchErrorKind::InvalidPointer)?;
match *parent {
Value::Object(ref mut obj) => match obj.remove(unescape(last_unescaped).as_ref()) {
None => Err(PatchErrorKind::InvalidPointer),
Some(val) => Ok(val),
},
Value::Array(ref mut arr) if allow_last && last_unescaped == "-" => Ok(arr.pop().unwrap()),
Value::Array(ref mut arr) => {
let idx = parse_index(last_unescaped, arr.len())?;
Ok(arr.remove(idx))
}
_ => Err(PatchErrorKind::InvalidPointer),
}
}
fn replace(doc: &mut Value, path: &str, value: Value) -> Result<Value, PatchErrorKind> {
let target = doc
.pointer_mut(path)
.ok_or(PatchErrorKind::InvalidPointer)?;
Ok(std::mem::replace(target, value))
}
fn mov(
doc: &mut Value,
from: &str,
path: &str,
allow_last: bool,
) -> Result<Option<Value>, PatchErrorKind> {
if path.starts_with(from) && path[from.len()..].starts_with('/') {
return Err(PatchErrorKind::CannotMoveInsideItself);
}
let val = remove(doc, from, allow_last).map_err(|err| match err {
PatchErrorKind::InvalidPointer => PatchErrorKind::InvalidFromPointer,
err => err,
})?;
add(doc, path, val)
}
fn copy(doc: &mut Value, from: &str, path: &str) -> Result<Option<Value>, PatchErrorKind> {
let source = doc
.pointer(from)
.ok_or(PatchErrorKind::InvalidFromPointer)?
.clone();
add(doc, path, source)
}
fn test(doc: &Value, path: &str, expected: &Value) -> Result<(), PatchErrorKind> {
let target = doc.pointer(path).ok_or(PatchErrorKind::InvalidPointer)?;
if *target == *expected {
Ok(())
} else {
Err(PatchErrorKind::TestFailed)
}
}
pub fn patch(doc: &mut Value, patch: &[PatchOperation]) -> Result<(), PatchError> {
apply_patches(doc, 0, patch)
}
fn apply_patches(
doc: &mut Value,
operation: usize,
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, op.value.clone())
.map_err(|e| translate_error(e, operation, &op.path))?;
apply_patches(doc, operation + 1, tail).map_err(move |e| {
match prev {
None => remove(doc, &op.path, true).unwrap(),
Some(v) => add(doc, &op.path, v).unwrap().unwrap(),
};
e
})
}
PatchOperation::Remove(ref op) => {
let prev = remove(doc, &op.path, false)
.map_err(|e| translate_error(e, operation, &op.path))?;
apply_patches(doc, operation + 1, tail).map_err(move |e| {
assert!(add(doc, &op.path, prev).unwrap().is_none());
e
})
}
PatchOperation::Replace(ref op) => {
let prev = replace(doc, &op.path, op.value.clone())
.map_err(|e| translate_error(e, operation, &op.path))?;
apply_patches(doc, operation + 1, tail).map_err(move |e| {
replace(doc, &op.path, prev).unwrap();
e
})
}
PatchOperation::Move(ref op) => {
let prev = mov(doc, op.from.as_str(), &op.path, false)
.map_err(|e| translate_error(e, operation, &op.path))?;
apply_patches(doc, operation + 1, tail).map_err(move |e| {
mov(doc, &op.path, op.from.as_str(), true).unwrap();
if let Some(prev) = prev {
assert!(add(doc, &op.path, prev).unwrap().is_none());
}
e
})
}
PatchOperation::Copy(ref op) => {
let prev = copy(doc, op.from.as_str(), &op.path)
.map_err(|e| translate_error(e, operation, &op.path))?;
apply_patches(doc, operation + 1, tail).map_err(move |e| {
match prev {
None => remove(doc, &op.path, true).unwrap(),
Some(v) => add(doc, &op.path, v).unwrap().unwrap(),
};
e
})
}
PatchOperation::Test(ref op) => {
test(doc, &op.path, &op.value).map_err(|e| translate_error(e, operation, &op.path))?;
apply_patches(doc, operation + 1, tail)
}
}
}
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);
}
}
}