chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

358
vendor/json-patch/src/diff.rs vendored Normal file
View File

@@ -0,0 +1,358 @@
use crate::Patch;
use jsonptr::PointerBuf;
use serde_json::{Map, Value};
fn diff_impl(left: &Value, right: &Value, pointer: &mut PointerBuf, patch: &mut super::Patch) {
match (left, right) {
(Value::Object(ref left_obj), Value::Object(ref right_obj)) => {
diff_object(left_obj, right_obj, pointer, patch);
}
(Value::Array(ref left_array), Value::Array(ref ref_array)) => {
diff_array(left_array, ref_array, pointer, patch);
}
(_, _) if left == right => {
// Nothing to do
}
(_, _) => {
// Values are different, replace the value at the path
patch
.0
.push(super::PatchOperation::Replace(super::ReplaceOperation {
path: pointer.clone(),
value: right.clone(),
}));
}
}
}
fn diff_array(left: &[Value], right: &[Value], pointer: &mut PointerBuf, patch: &mut Patch) {
let len = left.len().max(right.len());
let mut shift = 0usize;
for idx in 0..len {
pointer.push_back(idx - shift);
match (left.get(idx), right.get(idx)) {
(Some(left), Some(right)) => {
// Both array have an element at this index
diff_impl(left, right, pointer, patch);
}
(Some(_left), None) => {
// The left array has an element at this index, but not the right
shift += 1;
patch
.0
.push(super::PatchOperation::Remove(super::RemoveOperation {
path: pointer.clone(),
}));
}
(None, Some(right)) => {
// The right array has an element at this index, but not the left
patch
.0
.push(super::PatchOperation::Add(super::AddOperation {
path: pointer.clone(),
value: right.clone(),
}));
}
(None, None) => {
unreachable!()
}
}
pointer.pop_back();
}
}
fn diff_object(
left: &Map<String, Value>,
right: &Map<String, Value>,
pointer: &mut PointerBuf,
patch: &mut Patch,
) {
// Add or replace keys in the right object
for (key, right_value) in right {
pointer.push_back(key);
match left.get(key) {
Some(left_value) => {
diff_impl(left_value, right_value, pointer, patch);
}
None => {
patch
.0
.push(super::PatchOperation::Add(super::AddOperation {
path: pointer.clone(),
value: right_value.clone(),
}));
}
}
pointer.pop_back();
}
// Remove keys that are not in the right object
for key in left.keys() {
if !right.contains_key(key) {
pointer.push_back(key);
patch
.0
.push(super::PatchOperation::Remove(super::RemoveOperation {
path: pointer.clone(),
}));
pointer.pop_back();
}
}
}
/// Diff two JSON documents and generate a JSON Patch (RFC 6902).
///
/// # Example
/// Diff two JSONs:
///
/// ```rust
/// #[macro_use]
/// use json_patch::{Patch, patch, diff};
/// use serde_json::{json, from_value};
///
/// # pub fn main() {
/// let left = json!({
/// "title": "Goodbye!",
/// "author" : {
/// "givenName" : "John",
/// "familyName" : "Doe"
/// },
/// "tags":[ "example", "sample" ],
/// "content": "This will be unchanged"
/// });
///
/// let right = json!({
/// "title": "Hello!",
/// "author" : {
/// "givenName" : "John"
/// },
/// "tags": [ "example" ],
/// "content": "This will be unchanged",
/// "phoneNumber": "+01-123-456-7890"
/// });
///
/// let p = diff(&left, &right);
/// assert_eq!(p, from_value::<Patch>(json!([
/// { "op": "replace", "path": "/title", "value": "Hello!" },
/// { "op": "remove", "path": "/author/familyName" },
/// { "op": "remove", "path": "/tags/1" },
/// { "op": "add", "path": "/phoneNumber", "value": "+01-123-456-7890" },
/// ])).unwrap());
///
/// let mut doc = left.clone();
/// patch(&mut doc, &p).unwrap();
/// assert_eq!(doc, right);
///
/// # }
/// ```
pub fn diff(left: &Value, right: &Value) -> super::Patch {
let mut patch = super::Patch::default();
let mut path = PointerBuf::new();
diff_impl(left, right, &mut path, &mut patch);
patch
}
#[cfg(test)]
mod tests {
use serde_json::{json, Value};
#[test]
pub fn replace_all() {
let mut left = json!({"title": "Hello!"});
let patch = super::diff(&left, &Value::Null);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": null },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
}
#[test]
pub fn diff_empty_key() {
let mut left = json!({"title": "Something", "": "Hello!"});
let right = json!({"title": "Something", "": "Bye!"});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "/", "value": "Bye!" },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn add_all() {
let right = json!({"title": "Hello!"});
let patch = super::diff(&Value::Null, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": { "title": "Hello!" } },
]))
.unwrap()
);
let mut left = Value::Null;
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn remove_all() {
let mut left = json!(["hello", "bye"]);
let right = json!([]);
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/0" },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn remove_tail() {
let mut left = json!(["hello", "bye", "hi"]);
let right = json!(["hello"]);
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/1" },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn add_tail() {
let mut left = json!(["hello"]);
let right = json!(["hello", "bye", "hi"]);
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "add", "path": "/1", "value": "bye" },
{ "op": "add", "path": "/2", "value": "hi" }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn replace_object() {
let mut left = json!(["hello", "bye"]);
let right = json!({"hello": "bye"});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "", "value": {"hello": "bye"} }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
fn escape_json_keys() {
let mut left = json!({
"/slashed/path/with/~": 1
});
let right = json!({
"/slashed/path/with/~": 2,
});
let patch = super::diff(&left, &right);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn replace_object_array() {
let mut left = json!({ "style": { "ref": {"name": "name"} } });
let right = json!({ "style": [{ "ref": {"hello": "hello"} }]});
let patch = crate::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "/style", "value": [{ "ref": {"hello": "hello"} }] },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn replace_array_object() {
let mut left = json!({ "style": [{ "ref": {"hello": "hello"} }]});
let right = json!({ "style": { "ref": {"name": "name"} } });
let patch = crate::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "replace", "path": "/style", "value": { "ref": {"name": "name"} } },
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn remove_keys() {
let mut left = json!({"first": 1, "second": 2, "third": 3});
let right = json!({"first": 1, "second": 2});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "remove", "path": "/third" }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
#[test]
pub fn add_keys() {
let mut left = json!({"first": 1, "second": 2});
let right = json!({"first": 1, "second": 2, "third": 3});
let patch = super::diff(&left, &right);
assert_eq!(
patch,
serde_json::from_value(json!([
{ "op": "add", "path": "/third", "value": 3 }
]))
.unwrap()
);
crate::patch(&mut left, &patch).unwrap();
assert_eq!(left, right);
}
}

683
vendor/json-patch/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,683 @@
//! A [JSON Patch (RFC 6902)](https://tools.ietf.org/html/rfc6902) and
//! [JSON Merge Patch (RFC 7396)](https://tools.ietf.org/html/rfc7396) implementation for Rust.
//!
//! # Usage
//!
//! Add this to your *Cargo.toml*:
//! ```toml
//! [dependencies]
//! json-patch = "*"
//! ```
//!
//! # Examples
//! Create and patch document using JSON Patch:
//!
//! ```rust
//! #[macro_use]
//! use json_patch::{Patch, patch};
//! use serde_json::{from_value, json};
//!
//! # pub fn main() {
//! let mut doc = json!([
//! { "name": "Andrew" },
//! { "name": "Maxim" }
//! ]);
//!
//! let p: Patch = from_value(json!([
//! { "op": "test", "path": "/0/name", "value": "Andrew" },
//! { "op": "add", "path": "/0/happy", "value": true }
//! ])).unwrap();
//!
//! patch(&mut doc, &p).unwrap();
//! assert_eq!(doc, json!([
//! { "name": "Andrew", "happy": true },
//! { "name": "Maxim" }
//! ]));
//!
//! # }
//! ```
//!
//! Create and patch document using JSON Merge Patch:
//!
//! ```rust
//! #[macro_use]
//! use json_patch::merge;
//! use serde_json::json;
//!
//! # pub fn main() {
//! let mut doc = json!({
//! "title": "Goodbye!",
//! "author" : {
//! "givenName" : "John",
//! "familyName" : "Doe"
//! },
//! "tags":[ "example", "sample" ],
//! "content": "This will be unchanged"
//! });
//!
//! let patch = json!({
//! "title": "Hello!",
//! "phoneNumber": "+01-123-456-7890",
//! "author": {
//! "familyName": null
//! },
//! "tags": [ "example" ]
//! });
//!
//! merge(&mut doc, &patch);
//! assert_eq!(doc, json!({
//! "title": "Hello!",
//! "author" : {
//! "givenName" : "John"
//! },
//! "tags": [ "example" ],
//! "content": "This will be unchanged",
//! "phoneNumber": "+01-123-456-7890"
//! }));
//! # }
//! ```
#![warn(missing_docs)]
use jsonptr::{index::Index, Pointer, PointerBuf};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::fmt::{self, Display, Formatter};
use thiserror::Error;
// So users can instance `jsonptr::PointerBuf` and others without
// having to explicitly match our `jsonptr` version.
pub use jsonptr;
#[cfg(feature = "diff")]
mod diff;
#[cfg(feature = "diff")]
pub use self::diff::diff;
struct WriteAdapter<'a>(&'a mut dyn fmt::Write);
impl std::io::Write for WriteAdapter<'_> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
let s = std::str::from_utf8(buf).unwrap();
self.0
.write_str(s)
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))?;
Ok(buf.len())
}
fn flush(&mut self) -> Result<(), std::io::Error> {
Ok(())
}
}
macro_rules! impl_display {
($name:ident) => {
impl Display for $name {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let alternate = f.alternate();
if alternate {
serde_json::to_writer_pretty(WriteAdapter(f), self)
.map_err(|_| std::fmt::Error)?;
} else {
serde_json::to_writer(WriteAdapter(f), self).map_err(|_| std::fmt::Error)?;
}
Ok(())
}
}
};
}
/// Representation of JSON Patch (list of patch operations)
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Patch(pub Vec<PatchOperation>);
impl_display!(Patch);
impl std::ops::Deref for Patch {
type Target = [PatchOperation];
fn deref(&self) -> &[PatchOperation] {
&self.0
}
}
/// JSON Patch 'add' operation representation
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct AddOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub path: PointerBuf,
/// Value to add to the target location.
pub value: Value,
}
impl_display!(AddOperation);
/// JSON Patch 'remove' operation representation
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RemoveOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub path: PointerBuf,
}
impl_display!(RemoveOperation);
/// JSON Patch 'replace' operation representation
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ReplaceOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub path: PointerBuf,
/// Value to replace with.
pub value: Value,
}
impl_display!(ReplaceOperation);
/// JSON Patch 'move' operation representation
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MoveOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// to move value from.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub from: PointerBuf,
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub path: PointerBuf,
}
impl_display!(MoveOperation);
/// JSON Patch 'copy' operation representation
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct CopyOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// to copy value from.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub from: PointerBuf,
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub path: PointerBuf,
}
impl_display!(CopyOperation);
/// JSON Patch 'test' operation representation
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct TestOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
#[cfg_attr(feature = "schemars", schemars(schema_with = "String::json_schema"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub path: PointerBuf,
/// Value to test against.
pub value: Value,
}
impl_display!(TestOperation);
/// JSON Patch single patch operation
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(tag = "op")]
#[serde(rename_all = "lowercase")]
pub enum PatchOperation {
/// 'add' operation
Add(AddOperation),
/// 'remove' operation
Remove(RemoveOperation),
/// 'replace' operation
Replace(ReplaceOperation),
/// 'move' operation
Move(MoveOperation),
/// 'copy' operation
Copy(CopyOperation),
/// 'test' operation
Test(TestOperation),
}
impl_display!(PatchOperation);
impl PatchOperation {
/// Returns a reference to the path the operation applies to.
pub fn path(&self) -> &Pointer {
match self {
Self::Add(op) => &op.path,
Self::Remove(op) => &op.path,
Self::Replace(op) => &op.path,
Self::Move(op) => &op.path,
Self::Copy(op) => &op.path,
Self::Test(op) => &op.path,
}
}
}
impl Default for PatchOperation {
fn default() -> Self {
PatchOperation::Test(TestOperation::default())
}
}
/// This type represents all possible errors that can occur when applying JSON patch
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PatchErrorKind {
/// `test` operation failed because values did not match.
#[error("value did not match")]
TestFailed,
/// `from` JSON pointer in a `move` or a `copy` operation was incorrect.
#[error("\"from\" path is invalid")]
InvalidFromPointer,
/// `path` JSON pointer is incorrect.
#[error("path is invalid")]
InvalidPointer,
/// `move` operation failed because target is inside the `from` location.
#[error("cannot move the value inside itself")]
CannotMoveInsideItself,
}
impl From<jsonptr::index::ParseIndexError> for PatchErrorKind {
fn from(_: jsonptr::index::ParseIndexError) -> Self {
Self::InvalidPointer
}
}
impl From<jsonptr::index::OutOfBoundsError> for PatchErrorKind {
fn from(_: jsonptr::index::OutOfBoundsError) -> Self {
Self::InvalidPointer
}
}
/// This type represents all possible errors that can occur when applying JSON patch
#[derive(Debug, Error)]
#[error("operation '/{operation}' failed at path '{path}': {kind}")]
#[non_exhaustive]
pub struct PatchError {
/// Index of the operation that has failed.
pub operation: usize,
/// `path` of the operation.
pub path: PointerBuf,
/// Kind of the error.
pub kind: PatchErrorKind,
}
fn translate_error(kind: PatchErrorKind, operation: usize, path: &Pointer) -> PatchError {
PatchError {
operation,
path: path.to_owned(),
kind,
}
}
fn add(doc: &mut Value, path: &Pointer, value: Value) -> Result<Option<Value>, PatchErrorKind> {
let Some((parent, last)) = path.split_back() else {
// empty path, add is replace the value wholesale
return Ok(Some(std::mem::replace(doc, value)));
};
let mut parent = doc
.pointer_mut(parent.as_str())
.ok_or(PatchErrorKind::InvalidPointer)?;
match &mut parent {
Value::Object(obj) => Ok(obj.insert(last.decoded().into_owned(), value)),
Value::Array(arr) => {
let idx = last.to_index()?.for_len_incl(arr.len())?;
arr.insert(idx, value);
Ok(None)
}
_ => Err(PatchErrorKind::InvalidPointer),
}
}
fn remove(doc: &mut Value, path: &Pointer, allow_last: bool) -> Result<Value, PatchErrorKind> {
let Some((parent, last)) = path.split_back() else {
return Err(PatchErrorKind::InvalidPointer);
};
let mut parent = doc
.pointer_mut(parent.as_str())
.ok_or(PatchErrorKind::InvalidPointer)?;
match &mut parent {
Value::Object(obj) => match obj.remove(last.decoded().as_ref()) {
None => Err(PatchErrorKind::InvalidPointer),
Some(val) => Ok(val),
},
// XXX: is this really correct? semantically it seems off, `-` refers to an empty position, not the last element
Value::Array(arr) if allow_last && matches!(last.to_index(), Ok(Index::Next)) => {
Ok(arr.pop().unwrap())
}
Value::Array(arr) => {
let idx = last.to_index()?.for_len(arr.len())?;
Ok(arr.remove(idx))
}
_ => Err(PatchErrorKind::InvalidPointer),
}
}
fn replace(doc: &mut Value, path: &Pointer, value: Value) -> Result<Value, PatchErrorKind> {
let target = doc
.pointer_mut(path.as_str())
.ok_or(PatchErrorKind::InvalidPointer)?;
Ok(std::mem::replace(target, value))
}
fn mov(
doc: &mut Value,
from: &Pointer,
path: &Pointer,
allow_last: bool,
) -> Result<Option<Value>, PatchErrorKind> {
// Check we are not moving inside own child
if path.starts_with(from) && path.len() != from.len() {
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: &Pointer, path: &Pointer) -> Result<Option<Value>, PatchErrorKind> {
let source = doc
.pointer(from.as_str())
.ok_or(PatchErrorKind::InvalidFromPointer)?
.clone();
add(doc, path, source)
}
fn test(doc: &Value, path: &Pointer, expected: &Value) -> Result<(), PatchErrorKind> {
let target = doc
.pointer(path.as_str())
.ok_or(PatchErrorKind::InvalidPointer)?;
if *target == *expected {
Ok(())
} else {
Err(PatchErrorKind::TestFailed)
}
}
/// Patch provided JSON document (given as `serde_json::Value`) in-place. If any of the patch is
/// failed, all previous operations are reverted. In case of internal error resulting in panic,
/// document might be left in inconsistent state.
///
/// # Example
/// Create and patch document:
///
/// ```rust
/// #[macro_use]
/// use json_patch::{Patch, patch};
/// use serde_json::{from_value, json};
///
/// # pub fn main() {
/// let mut doc = json!([
/// { "name": "Andrew" },
/// { "name": "Maxim" }
/// ]);
///
/// let p: Patch = from_value(json!([
/// { "op": "test", "path": "/0/name", "value": "Andrew" },
/// { "op": "add", "path": "/0/happy", "value": true }
/// ])).unwrap();
///
/// patch(&mut doc, &p).unwrap();
/// assert_eq!(doc, json!([
/// { "name": "Andrew", "happy": true },
/// { "name": "Maxim" }
/// ]));
///
/// # }
/// ```
pub fn patch(doc: &mut Value, patch: &[PatchOperation]) -> Result<(), PatchError> {
let mut undo_stack = Vec::with_capacity(patch.len());
if let Err(e) = apply_patches(doc, patch, Some(&mut undo_stack)) {
if let Err(e) = undo_patches(doc, &undo_stack) {
unreachable!("unable to undo applied patches: {e}")
}
return Err(e);
}
Ok(())
}
/// Patch provided JSON document (given as `serde_json::Value`) in-place. Different from [`patch`]
/// if any patch failed, the document is left in an inconsistent state. In case of internal error
/// resulting in panic, document might be left in inconsistent state.
///
/// # Example
/// Create and patch document:
///
/// ```rust
/// #[macro_use]
/// use json_patch::{Patch, patch_unsafe};
/// use serde_json::{from_value, json};
///
/// # pub fn main() {
/// let mut doc = json!([
/// { "name": "Andrew" },
/// { "name": "Maxim" }
/// ]);
///
/// let p: Patch = from_value(json!([
/// { "op": "test", "path": "/0/name", "value": "Andrew" },
/// { "op": "add", "path": "/0/happy", "value": true }
/// ])).unwrap();
///
/// patch_unsafe(&mut doc, &p).unwrap();
/// assert_eq!(doc, json!([
/// { "name": "Andrew", "happy": true },
/// { "name": "Maxim" }
/// ]));
///
/// # }
/// ```
pub fn patch_unsafe(doc: &mut Value, patch: &[PatchOperation]) -> Result<(), PatchError> {
apply_patches(doc, patch, None)
}
/// Undoes operations performed by `apply_patches`. This is useful to recover the original document
/// in case of an error.
fn undo_patches(doc: &mut Value, undo_patches: &[PatchOperation]) -> Result<(), PatchError> {
for (operation, patch) in undo_patches.iter().enumerate().rev() {
match patch {
PatchOperation::Add(op) => {
add(doc, &op.path, op.value.clone())
.map_err(|e| translate_error(e, operation, &op.path))?;
}
PatchOperation::Remove(op) => {
remove(doc, &op.path, true).map_err(|e| translate_error(e, operation, &op.path))?;
}
PatchOperation::Replace(op) => {
replace(doc, &op.path, op.value.clone())
.map_err(|e| translate_error(e, operation, &op.path))?;
}
PatchOperation::Move(op) => {
mov(doc, &op.from, &op.path, true)
.map_err(|e| translate_error(e, operation, &op.path))?;
}
PatchOperation::Copy(op) => {
copy(doc, &op.from, &op.path)
.map_err(|e| translate_error(e, operation, &op.path))?;
}
_ => unreachable!(),
}
}
Ok(())
}
// Apply patches while tracking all the changes being made so they can be reverted back in case
// subsequent patches fail. The inverse of all state changes is recorded in the `undo_stack` which
// can be reapplied using `undo_patches` to get back to the original document.
fn apply_patches(
doc: &mut Value,
patches: &[PatchOperation],
undo_stack: Option<&mut Vec<PatchOperation>>,
) -> Result<(), PatchError> {
for (operation, patch) in patches.iter().enumerate() {
match patch {
PatchOperation::Add(ref op) => {
let prev = add(doc, &op.path, op.value.clone())
.map_err(|e| translate_error(e, operation, &op.path))?;
if let Some(&mut ref mut undo_stack) = undo_stack {
undo_stack.push(match prev {
None => PatchOperation::Remove(RemoveOperation {
path: op.path.clone(),
}),
Some(v) => PatchOperation::Add(AddOperation {
path: op.path.clone(),
value: v,
}),
})
}
}
PatchOperation::Remove(ref op) => {
let prev = remove(doc, &op.path, false)
.map_err(|e| translate_error(e, operation, &op.path))?;
if let Some(&mut ref mut undo_stack) = undo_stack {
undo_stack.push(PatchOperation::Add(AddOperation {
path: op.path.clone(),
value: prev,
}))
}
}
PatchOperation::Replace(ref op) => {
let prev = replace(doc, &op.path, op.value.clone())
.map_err(|e| translate_error(e, operation, &op.path))?;
if let Some(&mut ref mut undo_stack) = undo_stack {
undo_stack.push(PatchOperation::Replace(ReplaceOperation {
path: op.path.clone(),
value: prev,
}))
}
}
PatchOperation::Move(ref op) => {
let prev = mov(doc, &op.from, &op.path, false)
.map_err(|e| translate_error(e, operation, &op.path))?;
if let Some(&mut ref mut undo_stack) = undo_stack {
if let Some(prev) = prev {
undo_stack.push(PatchOperation::Add(AddOperation {
path: op.path.clone(),
value: prev,
}));
}
undo_stack.push(PatchOperation::Move(MoveOperation {
from: op.path.clone(),
path: op.from.clone(),
}));
}
}
PatchOperation::Copy(ref op) => {
let prev = copy(doc, &op.from, &op.path)
.map_err(|e| translate_error(e, operation, &op.path))?;
if let Some(&mut ref mut undo_stack) = undo_stack {
undo_stack.push(match prev {
None => PatchOperation::Remove(RemoveOperation {
path: op.path.clone(),
}),
Some(v) => PatchOperation::Add(AddOperation {
path: op.path.clone(),
value: v,
}),
})
}
}
PatchOperation::Test(ref op) => {
test(doc, &op.path, &op.value)
.map_err(|e| translate_error(e, operation, &op.path))?;
}
}
}
Ok(())
}
/// Patch provided JSON document (given as `serde_json::Value`) in place with JSON Merge Patch
/// (RFC 7396).
///
/// # Example
/// Create and patch document:
///
/// ```rust
/// #[macro_use]
/// use json_patch::merge;
/// use serde_json::json;
///
/// # pub fn main() {
/// let mut doc = json!({
/// "title": "Goodbye!",
/// "author" : {
/// "givenName" : "John",
/// "familyName" : "Doe"
/// },
/// "tags":[ "example", "sample" ],
/// "content": "This will be unchanged"
/// });
///
/// let patch = json!({
/// "title": "Hello!",
/// "phoneNumber": "+01-123-456-7890",
/// "author": {
/// "familyName": null
/// },
/// "tags": [ "example" ]
/// });
///
/// merge(&mut doc, &patch);
/// assert_eq!(doc, json!({
/// "title": "Hello!",
/// "author" : {
/// "givenName" : "John"
/// },
/// "tags": [ "example" ],
/// "content": "This will be unchanged",
/// "phoneNumber": "+01-123-456-7890"
/// }));
/// # }
/// ```
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);
}
}
}