Skip to content

Commit

Permalink
migrate to jsonptr
Browse files Browse the repository at this point in the history
  • Loading branch information
asmello authored and idubrov committed Mar 19, 2024
1 parent fad9f28 commit 72f92a5
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 91 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ default = ["diff"]
diff = ["treediff"]

[dependencies]
jsonptr = "0.4.7"
serde = { version = "1.0.159", features = ["derive"] }
serde_json = "1.0.95"
thiserror = "1.0.40"
Expand Down
18 changes: 8 additions & 10 deletions benches/generator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use json_patch::{AddOperation, Patch, PatchOperation, RemoveOperation};
use jsonptr::Pointer;
use rand::distributions::Alphanumeric;
use rand::prelude::*;
use serde_json::{Map, Value};
use std::fmt::Write;

pub struct Params {
pub array_size: usize,
Expand Down Expand Up @@ -98,28 +98,26 @@ pub fn gen_add_remove_patches<R: Rng>(
vec
}

fn all_leafs(value: &Value) -> Vec<String> {
fn all_leafs(value: &Value) -> Vec<Pointer> {
let mut result = Vec::new();
collect_leafs(value, &mut String::new(), &mut result);
collect_leafs(value, &mut Pointer::root(), &mut result);
result
}

fn collect_leafs(value: &Value, prefix: &mut String, result: &mut Vec<String>) {
fn collect_leafs(value: &Value, prefix: &mut Pointer, result: &mut Vec<Pointer>) {
match *value {
Value::Array(ref arr) => {
for (idx, val) in arr.iter().enumerate() {
let l = prefix.len();
write!(prefix, "/{}", idx).unwrap();
prefix.push_back(idx.into());
collect_leafs(val, prefix, result);
prefix.truncate(l);
prefix.pop_back();
}
}
Value::Object(ref map) => {
for (key, val) in map.iter() {
let l = prefix.len();
write!(prefix, "/{}", key).unwrap();
prefix.push_back(key.into());
collect_leafs(val, prefix, result);
prefix.truncate(l);
prefix.pop_back();
}
}
_ => {
Expand Down
48 changes: 12 additions & 36 deletions src/diff.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,51 @@
use jsonptr::Pointer;
use serde_json::Value;

struct PatchDiffer {
path: String,
path: Pointer,
patch: super::Patch,
shift: usize,
}

impl PatchDiffer {
fn new() -> Self {
Self {
path: "".to_string(),
path: Pointer::root(),
patch: super::Patch(Vec::new()),
shift: 0,
}
}
}

impl<'a> treediff::Delegate<'a, treediff::value::Key, Value> for PatchDiffer {
fn push(&mut self, key: &treediff::value::Key) {
use std::fmt::Write;
self.path.push('/');
match *key {
treediff::value::Key::Index(idx) => write!(self.path, "{}", idx - self.shift).unwrap(),
treediff::value::Key::String(ref key) => append_path(&mut self.path, key),
match key {
treediff::value::Key::Index(idx) => self.path.push_back(idx.into()),
treediff::value::Key::String(key) => self.path.push_back(key.into()),
}
}

fn pop(&mut self) {
let pos = self.path.rfind('/').unwrap_or(0);
self.path.truncate(pos);
self.shift = 0;
self.path.pop_back();
}

fn removed<'b>(&mut self, k: &'b treediff::value::Key, _v: &'a Value) {
let len = self.path.len();
self.push(k);
self.patch
.0
.push(super::PatchOperation::Remove(super::RemoveOperation {
path: self.path.clone(),
}));
// Shift indices, we are deleting array elements
if let treediff::value::Key::Index(_) = k {
self.shift += 1;
}
self.path.truncate(len);
self.pop();
}

fn added(&mut self, k: &treediff::value::Key, v: &Value) {
let len = self.path.len();
self.push(k);
self.patch
.0
.push(super::PatchOperation::Add(super::AddOperation {
path: self.path.clone(),
value: v.clone(),
}));
self.path.truncate(len);
self.pop();
}

fn modified(&mut self, _old: &'a Value, new: &'a Value) {
Expand All @@ -69,19 +58,6 @@ impl<'a> treediff::Delegate<'a, treediff::value::Key, Value> for PatchDiffer {
}
}

fn append_path(path: &mut String, key: &str) {
path.reserve(key.len());
for ch in key.chars() {
if ch == '~' {
*path += "~0";
} else if ch == '/' {
*path += "~1";
} else {
path.push(ch);
}
}
}

/// Diff two JSON documents and generate a JSON Patch (RFC 6902).
///
/// # Example
Expand Down Expand Up @@ -191,7 +167,7 @@ mod tests {
p,
serde_json::from_value(json!([
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/1" },
]))
.unwrap()
);
Expand All @@ -206,7 +182,7 @@ mod tests {
p,
serde_json::from_value(json!([
{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/1" },
{ "op": "remove", "path": "/2" },
]))
.unwrap()
);
Expand All @@ -221,7 +197,7 @@ mod tests {
serde_json::from_value(json!([
{ "op": "add", "path": "/hello", "value": "bye" },
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/0" },
{ "op": "remove", "path": "/1" },
]))
.unwrap()
);
Expand Down
37 changes: 26 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
//! ```
#![warn(missing_docs)]

use jsonptr::Pointer;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::{
Expand Down Expand Up @@ -146,7 +147,7 @@ impl std::ops::Deref for Patch {
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.
pub path: String,
pub path: Pointer,
/// Value to add to the target location.
pub value: Value,
}
Expand All @@ -159,7 +160,7 @@ impl_display!(AddOperation);
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.
pub path: String,
pub path: Pointer,
}

impl_display!(RemoveOperation);
Expand All @@ -170,7 +171,7 @@ impl_display!(RemoveOperation);
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.
pub path: String,
pub path: Pointer,
/// Value to replace with.
pub value: Value,
}
Expand All @@ -183,10 +184,10 @@ impl_display!(ReplaceOperation);
pub struct MoveOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// to move value from.
pub from: String,
pub from: Pointer,
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
pub path: String,
pub path: Pointer,
}

impl_display!(MoveOperation);
Expand All @@ -197,10 +198,10 @@ impl_display!(MoveOperation);
pub struct CopyOperation {
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// to copy value from.
pub from: String,
pub from: Pointer,
/// JSON-Pointer value [RFC6901](https://tools.ietf.org/html/rfc6901) that references a location
/// within the target document where the operation is performed.
pub path: String,
pub path: Pointer,
}

impl_display!(CopyOperation);
Expand All @@ -211,7 +212,7 @@ impl_display!(CopyOperation);
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.
pub path: String,
pub path: Pointer,
/// Value to test against.
pub value: Value,
}
Expand Down Expand Up @@ -240,6 +241,20 @@ pub enum PatchOperation {

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,
}
}
}

/// This type represents all possible errors that can occur when applying JSON patch
#[derive(Debug, Error)]
#[non_exhaustive]
Expand All @@ -260,18 +275,18 @@ pub enum PatchErrorKind {

/// This type represents all possible errors that can occur when applying JSON patch
#[derive(Debug, Error)]
#[error("Operation '/{operation}' failed at path '{path}': {kind}")]
#[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: String,
pub path: Pointer,
/// Kind of the error.
pub kind: PatchErrorKind,
}

fn translate_error(kind: PatchErrorKind, operation: usize, path: &str) -> PatchError {
fn translate_error(kind: PatchErrorKind, operation: usize, path: &Pointer) -> PatchError {
PatchError {
operation,
path: path.to_owned(),
Expand Down
29 changes: 15 additions & 14 deletions tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use json_patch::{
AddOperation, CopyOperation, MoveOperation, Patch, PatchOperation, RemoveOperation,
ReplaceOperation, TestOperation,
};
use jsonptr::Pointer;
use serde_json::{from_str, from_value, json, Value};

#[test]
Expand All @@ -13,11 +14,11 @@ fn parse_from_value() {
patch,
Patch(vec![
PatchOperation::Add(AddOperation {
path: String::from("/a/b"),
path: Pointer::new(["a", "b"]),
value: Value::from(1),
}),
PatchOperation::Remove(RemoveOperation {
path: String::from("/c"),
path: Pointer::new(["c"]),
}),
])
);
Expand All @@ -37,11 +38,11 @@ fn parse_from_string() {
patch,
Patch(vec![
PatchOperation::Add(AddOperation {
path: String::from("/a/b"),
path: Pointer::new(["a", "b"]),
value: Value::from(1),
}),
PatchOperation::Remove(RemoveOperation {
path: String::from("/c"),
path: Pointer::new(["c"]),
}),
])
);
Expand All @@ -59,7 +60,7 @@ fn serialize_patch() {
#[test]
fn display_add_operation() {
let op = PatchOperation::Add(AddOperation {
path: String::from("/a/b/c"),
path: Pointer::new(["a", "b", "c"]),
value: json!(["hello", "bye"]),
});
assert_eq!(
Expand All @@ -82,7 +83,7 @@ fn display_add_operation() {
#[test]
fn display_remove_operation() {
let op = PatchOperation::Remove(RemoveOperation {
path: String::from("/a/b/c"),
path: Pointer::new(["a", "b", "c"]),
});
assert_eq!(op.to_string(), r#"{"op":"remove","path":"/a/b/c"}"#);
assert_eq!(
Expand All @@ -97,7 +98,7 @@ fn display_remove_operation() {
#[test]
fn display_replace_operation() {
let op = PatchOperation::Replace(ReplaceOperation {
path: String::from("/a/b/c"),
path: Pointer::new(["a", "b", "c"]),
value: json!(42),
});
assert_eq!(
Expand All @@ -117,8 +118,8 @@ fn display_replace_operation() {
#[test]
fn display_move_operation() {
let op = PatchOperation::Move(MoveOperation {
from: String::from("/a/b/c"),
path: String::from("/a/b/d"),
from: Pointer::new(["a", "b", "c"]),
path: Pointer::new(["a", "b", "d"]),
});
assert_eq!(
op.to_string(),
Expand All @@ -137,8 +138,8 @@ fn display_move_operation() {
#[test]
fn display_copy_operation() {
let op = PatchOperation::Copy(CopyOperation {
from: String::from("/a/b/d"),
path: String::from("/a/b/e"),
from: Pointer::new(["a", "b", "d"]),
path: Pointer::new(["a", "b", "e"]),
});
assert_eq!(
op.to_string(),
Expand All @@ -157,7 +158,7 @@ fn display_copy_operation() {
#[test]
fn display_test_operation() {
let op = PatchOperation::Test(TestOperation {
path: String::from("/a/b/c"),
path: Pointer::new(["a", "b", "c"]),
value: json!("hello"),
});
assert_eq!(
Expand All @@ -178,11 +179,11 @@ fn display_test_operation() {
fn display_patch() {
let patch = Patch(vec![
PatchOperation::Add(AddOperation {
path: String::from("/a/b/c"),
path: Pointer::new(["a", "b", "c"]),
value: json!(["hello", "bye"]),
}),
PatchOperation::Remove(RemoveOperation {
path: String::from("/a/b/c"),
path: Pointer::new(["a", "b", "c"]),
}),
]);

Expand Down
Loading

0 comments on commit 72f92a5

Please sign in to comment.