platform_value/patch/
mod.rs

1//! A Platform Value Patch and Platform Value Merge Patch implementation for Rust.
2//!
3//! # Examples
4//! Create and patch document using Platform Value Patch:
5//!
6//! ```rust
7//! #[macro_use]
8//! use platform_value::{Patch, patch, from_value, platform_value};
9//!
10//! # pub fn main() {
11//! let mut doc = platform_value!([
12//!     { "name": "Andrew" },
13//!     { "name": "Maxim" }
14//! ]);
15//!
16//! let p: Patch = from_value(platform_value!([
17//!   { "op": "test", "path": "/0/name", "value": "Andrew" },
18//!   { "op": "add", "path": "/0/happy", "value": true }
19//! ])).unwrap();
20//!
21//! patch(&mut doc, &p).unwrap();
22//! assert_eq!(doc, platform_value!([
23//!   { "name": "Andrew", "happy": true },
24//!   { "name": "Maxim" }
25//! ]));
26//!
27//! # }
28//! ```
29//!
30//! Create and patch document using Platform Value Merge Patch:
31//!
32//! ```rust
33//! #[macro_use]
34//! use platform_value::{patch::merge, platform_value};
35//!
36//! # pub fn main() {
37//! let mut doc = platform_value!({
38//!   "title": "Goodbye!",
39//!   "author" : {
40//!     "givenName" : "John",
41//!     "familyName" : "Doe"
42//!   },
43//!   "tags":[ "example", "sample" ],
44//!   "content": "This will be unchanged"
45//! });
46//!
47//! let patch = platform_value!({
48//!   "title": "Hello!",
49//!   "phoneNumber": "+01-123-456-7890",
50//!   "author": {
51//!     "familyName": null
52//!   },
53//!   "tags": [ "example" ]
54//! });
55//!
56//! merge(&mut doc, &patch);
57//! assert_eq!(doc, platform_value!({
58//!   "title": "Hello!",
59//!   "author" : {
60//!     "givenName" : "John"
61//!   },
62//!   "tags": [ "example" ],
63//!   "content": "This will be unchanged",
64//!   "phoneNumber": "+01-123-456-7890"
65//! }));
66//! # }
67//! ```
68
69pub use self::diff::diff;
70use crate::value_map::ValueMap;
71use crate::{Value, ValueMapHelper};
72use serde::{Deserialize, Serialize};
73use std::borrow::Cow;
74use thiserror::Error;
75mod diff;
76
77/// Representation of Platform Value Patch (list of patch operations)
78#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
79pub struct Patch(pub Vec<PatchOperation>);
80
81impl std::ops::Deref for Patch {
82    type Target = [PatchOperation];
83
84    fn deref(&self) -> &[PatchOperation] {
85        &self.0
86    }
87}
88
89/// Platform Value Patch 'add' operation representation
90#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
91pub struct AddOperation {
92    pub path: String,
93    /// Value to add to the target location.
94    pub value: Value,
95}
96
97/// Platform Value Patch 'remove' operation representation
98#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
99pub struct RemoveOperation {
100    pub path: String,
101}
102
103/// Platform Value Patch 'replace' operation representation
104#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
105pub struct ReplaceOperation {
106    /// The location within the target document where the operation is performed.
107    pub path: String,
108    /// Value to replace with.
109    pub value: Value,
110}
111
112/// Platform Value Patch 'move' operation representation
113#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
114pub struct MoveOperation {
115    /// The location to move value from.
116    pub from: String,
117    /// The location within the target document where the operation is performed.
118    pub path: String,
119}
120
121/// Platform Value Patch 'copy' operation representation
122#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
123pub struct CopyOperation {
124    /// The location to copy value from.
125    pub from: String,
126    /// The location within the target document where the operation is performed.
127    pub path: String,
128}
129
130/// Platform Value Patch 'test' operation representation
131#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
132pub struct TestOperation {
133    /// The location within the target document where the operation is performed.
134    pub path: String,
135    /// Value to test against.
136    pub value: Value,
137}
138
139/// Platform Value Patch single patch operation
140#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
141#[serde(tag = "op")]
142#[serde(rename_all = "lowercase")]
143pub enum PatchOperation {
144    /// 'add' operation
145    Add(AddOperation),
146    /// 'remove' operation
147    Remove(RemoveOperation),
148    /// 'replace' operation
149    Replace(ReplaceOperation),
150    /// 'move' operation
151    Move(MoveOperation),
152    /// 'copy' operation
153    Copy(CopyOperation),
154    /// 'test' operation
155    Test(TestOperation),
156}
157
158/// This type represents all possible errors that can occur when applying Platform Value patch
159#[derive(Debug, Error)]
160#[non_exhaustive]
161pub enum PatchErrorKind {
162    /// `test` operation failed because values did not match.
163    #[error("value did not match")]
164    TestFailed,
165    /// `from` Platform Value pointer in a `move` or a `copy` operation was incorrect.
166    #[error("\"from\" path is invalid")]
167    InvalidFromPointer,
168    /// `path` Platform Value pointer is incorrect.
169    #[error("path is invalid")]
170    InvalidPointer,
171    /// `move` operation failed because target is inside the `from` location.
172    #[error("cannot move the value inside itself")]
173    CannotMoveInsideItself,
174}
175
176/// This type represents all possible errors that can occur when applying Platform Value patch
177#[derive(Debug, Error)]
178#[error("Operation '/{operation}' failed at path '{path}': {kind}")]
179#[non_exhaustive]
180pub struct PatchError {
181    /// Index of the operation that has failed.
182    pub operation: usize,
183    /// `path` of the operation.
184    pub path: String,
185    /// Kind of the error.
186    pub kind: PatchErrorKind,
187}
188
189fn translate_error(kind: PatchErrorKind, operation: usize, path: &str) -> PatchError {
190    PatchError {
191        operation,
192        path: path.to_owned(),
193        kind,
194    }
195}
196
197fn unescape(s: &str) -> Cow<'_, str> {
198    if s.contains('~') {
199        Cow::Owned(s.replace("~1", "/").replace("~0", "~"))
200    } else {
201        Cow::Borrowed(s)
202    }
203}
204
205fn parse_index(str: &str, len: usize) -> Result<usize, PatchErrorKind> {
206    // RFC 6901 prohibits leading zeroes in index
207    if (str.starts_with('0') && str.len() != 1) || str.starts_with('+') {
208        return Err(PatchErrorKind::InvalidPointer);
209    }
210    match str.parse::<usize>() {
211        Ok(index) if index < len => Ok(index),
212        _ => Err(PatchErrorKind::InvalidPointer),
213    }
214}
215
216fn split_pointer(pointer: &str) -> Result<(&str, &str), PatchErrorKind> {
217    pointer
218        .rfind('/')
219        .ok_or(PatchErrorKind::InvalidPointer)
220        .map(|idx| (&pointer[0..idx], &pointer[idx + 1..]))
221}
222
223fn add(doc: &mut Value, path: &str, value: Value) -> Result<Option<Value>, PatchErrorKind> {
224    if path.is_empty() {
225        return Ok(Some(std::mem::replace(doc, value)));
226    }
227
228    let (parent, last_unescaped) = split_pointer(path)?;
229    let parent = doc
230        .pointer_mut(parent)
231        .ok_or(PatchErrorKind::InvalidPointer)?;
232
233    match *parent {
234        Value::Map(ref mut obj) => {
235            obj.insert_string_key_value(unescape(last_unescaped).into_owned(), value.clone());
236            Ok(Some(value))
237        }
238        Value::Array(ref mut arr) if last_unescaped == "-" => {
239            arr.push(value);
240            Ok(None)
241        }
242        Value::Array(ref mut arr) => {
243            let idx = parse_index(last_unescaped, arr.len() + 1)?;
244            arr.insert(idx, value);
245            Ok(None)
246        }
247        _ => Err(PatchErrorKind::InvalidPointer),
248    }
249}
250
251fn remove(doc: &mut Value, path: &str, allow_last: bool) -> Result<Value, PatchErrorKind> {
252    let (parent, last_unescaped) = split_pointer(path)?;
253    let parent = doc
254        .pointer_mut(parent)
255        .ok_or(PatchErrorKind::InvalidPointer)?;
256
257    match *parent {
258        Value::Map(ref mut obj) => match obj.remove_optional_key(unescape(last_unescaped).as_ref())
259        {
260            None => Err(PatchErrorKind::InvalidPointer),
261            Some(val) => Ok(val),
262        },
263        Value::Array(ref mut arr) if allow_last && last_unescaped == "-" => Ok(arr.pop().unwrap()),
264        Value::Array(ref mut arr) => {
265            let idx = parse_index(last_unescaped, arr.len())?;
266            Ok(arr.remove(idx))
267        }
268        _ => Err(PatchErrorKind::InvalidPointer),
269    }
270}
271
272fn replace(doc: &mut Value, path: &str, value: Value) -> Result<Value, PatchErrorKind> {
273    let target = doc
274        .pointer_mut(path)
275        .ok_or(PatchErrorKind::InvalidPointer)?;
276    Ok(std::mem::replace(target, value))
277}
278
279fn mov(
280    doc: &mut Value,
281    from: &str,
282    path: &str,
283    allow_last: bool,
284) -> Result<Option<Value>, PatchErrorKind> {
285    // Check we are not moving inside own child
286    if path.starts_with(from) && path[from.len()..].starts_with('/') {
287        return Err(PatchErrorKind::CannotMoveInsideItself);
288    }
289    let val = remove(doc, from, allow_last).map_err(|err| match err {
290        PatchErrorKind::InvalidPointer => PatchErrorKind::InvalidFromPointer,
291        err => err,
292    })?;
293    add(doc, path, val)
294}
295
296fn copy(doc: &mut Value, from: &str, path: &str) -> Result<Option<Value>, PatchErrorKind> {
297    let source = doc
298        .pointer(from)
299        .ok_or(PatchErrorKind::InvalidFromPointer)?
300        .clone();
301    add(doc, path, source)
302}
303
304fn test(doc: &Value, path: &str, expected: &Value) -> Result<(), PatchErrorKind> {
305    let target = doc.pointer(path).ok_or(PatchErrorKind::InvalidPointer)?;
306    if *target == *expected {
307        Ok(())
308    } else {
309        Err(PatchErrorKind::TestFailed)
310    }
311}
312
313/// Patch provided Platform Value document (given as `platform_value::Value`) in-place. If any of the patch is
314/// failed, all previous operations are reverted. In case of internal error resulting in panic,
315/// document might be left in inconsistent state.
316///
317/// # Example
318/// Create and patch document:
319///
320/// ```rust
321/// #[macro_use]
322/// use platform_value::{Patch, patch, from_value, platform_value};
323///
324/// # pub fn main() {
325/// let mut doc = platform_value!([
326///     { "name": "Andrew" },
327///     { "name": "Maxim" }
328/// ]);
329///
330/// let p: Patch = from_value(platform_value!([
331///   { "op": "test", "path": "/0/name", "value": "Andrew" },
332///   { "op": "add", "path": "/0/happy", "value": true }
333/// ])).unwrap();
334///
335/// patch(&mut doc, &p).unwrap();
336/// assert_eq!(doc, platform_value!([
337///   { "name": "Andrew", "happy": true },
338///   { "name": "Maxim" }
339/// ]));
340///
341/// # }
342/// ```
343pub fn patch(doc: &mut Value, patch: &[PatchOperation]) -> Result<(), PatchError> {
344    apply_patches(doc, 0, patch)
345}
346
347// Apply patches while tracking all the changes being made so they can be reverted back in case
348// subsequent patches fail. Uses stack recursion to keep the state.
349fn apply_patches(
350    doc: &mut Value,
351    operation: usize,
352    patches: &[PatchOperation],
353) -> Result<(), PatchError> {
354    let (patch, tail) = match patches.split_first() {
355        None => return Ok(()),
356        Some((patch, tail)) => (patch, tail),
357    };
358
359    match *patch {
360        PatchOperation::Add(ref op) => {
361            let prev = add(doc, &op.path, op.value.clone())
362                .map_err(|e| translate_error(e, operation, &op.path))?;
363            apply_patches(doc, operation + 1, tail).inspect_err(move |_| {
364                match prev {
365                    None => remove(doc, &op.path, true).unwrap(),
366                    Some(v) => add(doc, &op.path, v).unwrap().unwrap(),
367                };
368            })
369        }
370        PatchOperation::Remove(ref op) => {
371            let prev = remove(doc, &op.path, false)
372                .map_err(|e| translate_error(e, operation, &op.path))?;
373            apply_patches(doc, operation + 1, tail).inspect_err(move |_| {
374                assert!(add(doc, &op.path, prev).unwrap().is_none());
375            })
376        }
377        PatchOperation::Replace(ref op) => {
378            let prev = replace(doc, &op.path, op.value.clone())
379                .map_err(|e| translate_error(e, operation, &op.path))?;
380            apply_patches(doc, operation + 1, tail).inspect_err(move |_| {
381                replace(doc, &op.path, prev).unwrap();
382            })
383        }
384        PatchOperation::Move(ref op) => {
385            let prev = mov(doc, op.from.as_str(), &op.path, false)
386                .map_err(|e| translate_error(e, operation, &op.path))?;
387            apply_patches(doc, operation + 1, tail).inspect_err(move |_| {
388                mov(doc, &op.path, op.from.as_str(), true).unwrap();
389                if let Some(prev) = prev {
390                    assert!(add(doc, &op.path, prev).unwrap().is_none());
391                }
392            })
393        }
394        PatchOperation::Copy(ref op) => {
395            let prev = copy(doc, op.from.as_str(), &op.path)
396                .map_err(|e| translate_error(e, operation, &op.path))?;
397            apply_patches(doc, operation + 1, tail).inspect_err(move |_| {
398                match prev {
399                    None => remove(doc, &op.path, true).unwrap(),
400                    Some(v) => add(doc, &op.path, v).unwrap().unwrap(),
401                };
402            })
403        }
404        PatchOperation::Test(ref op) => {
405            test(doc, &op.path, &op.value).map_err(|e| translate_error(e, operation, &op.path))?;
406            apply_patches(doc, operation + 1, tail)
407        }
408    }
409}
410
411/// Patch provided Platform Value document (given as `platform_value::Value`) in place with Platform Value Merge Patch
412/// (RFC 7396).
413///
414/// # Example
415/// Create and patch document:
416///
417/// ```rust
418/// #[macro_use]
419/// use platform_value::{patch::merge, platform_value};
420///
421/// # pub fn main() {
422/// let mut doc = platform_value!({
423///   "title": "Goodbye!",
424///   "author" : {
425///     "givenName" : "John",
426///     "familyName" : "Doe"
427///   },
428///   "tags":[ "example", "sample" ],
429///   "content": "This will be unchanged"
430/// });
431///
432/// let patch = platform_value!({
433///   "title": "Hello!",
434///   "phoneNumber": "+01-123-456-7890",
435///   "author": {
436///     "familyName": null
437///   },
438///   "tags": [ "example" ]
439/// });
440///
441/// merge(&mut doc, &patch);
442///
443/// assert_eq!(doc, platform_value!({
444///   "title": "Hello!",
445///   "author" : {
446///     "givenName" : "John"
447///   },
448///   "tags": [ "example" ],
449///   "content": "This will be unchanged",
450///   "phoneNumber": "+01-123-456-7890"
451/// }));
452/// # }
453/// ```
454pub fn merge(doc: &mut Value, patch: &Value) {
455    if !patch.is_map() {
456        *doc = patch.clone();
457        return;
458    }
459
460    if !doc.is_map() {
461        *doc = Value::Map(ValueMap::new());
462    }
463    let map = doc.as_map_mut().unwrap();
464    for (key, value) in patch.as_map().unwrap() {
465        if value.is_null() {
466            map.remove_optional_key_value(key);
467        } else {
468            merge(map.get_key_by_value_mut_or_insert(key, Value::Null), value);
469        }
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476    use crate::{from_value, platform_value};
477
478    // ---------------------------------------------------------------
479    // add operation
480    // ---------------------------------------------------------------
481
482    #[test]
483    fn add_to_map_key() {
484        let mut doc = platform_value!({"a": 1});
485        let p: Patch = from_value(platform_value!([
486            { "op": "add", "path": "/b", "value": 2 }
487        ]))
488        .unwrap();
489        patch(&mut doc, &p).unwrap();
490        assert_eq!(doc.pointer("/b"), Some(&platform_value!(2)));
491    }
492
493    #[test]
494    fn add_to_array_push_with_dash() {
495        let mut doc = platform_value!({"arr": [1, 2]});
496        let p: Patch = from_value(platform_value!([
497            { "op": "add", "path": "/arr/-", "value": 3 }
498        ]))
499        .unwrap();
500        patch(&mut doc, &p).unwrap();
501        assert_eq!(doc, platform_value!({"arr": [1, 2, 3]}));
502    }
503
504    #[test]
505    fn add_to_array_insert_at_index() {
506        let mut doc = platform_value!({"arr": [1, 3]});
507        let p: Patch = from_value(platform_value!([
508            { "op": "add", "path": "/arr/1", "value": 2 }
509        ]))
510        .unwrap();
511        patch(&mut doc, &p).unwrap();
512        assert_eq!(doc, platform_value!({"arr": [1, 2, 3]}));
513    }
514
515    #[test]
516    fn add_empty_path_replaces_whole_document() {
517        let mut doc = platform_value!({"old": "value"});
518        let p: Patch = from_value(platform_value!([
519            { "op": "add", "path": "", "value": "replaced" }
520        ]))
521        .unwrap();
522        patch(&mut doc, &p).unwrap();
523        assert_eq!(doc, platform_value!("replaced"));
524    }
525
526    #[test]
527    fn add_to_nested_map() {
528        let mut doc = platform_value!({"a": {"b": 1}});
529        let p: Patch = from_value(platform_value!([
530            { "op": "add", "path": "/a/c", "value": 2 }
531        ]))
532        .unwrap();
533        patch(&mut doc, &p).unwrap();
534        assert_eq!(doc.pointer("/a/c"), Some(&platform_value!(2)));
535    }
536
537    #[test]
538    fn add_at_array_beginning() {
539        let mut doc = platform_value!([2, 3]);
540        let p: Patch = from_value(platform_value!([
541            { "op": "add", "path": "/0", "value": 1 }
542        ]))
543        .unwrap();
544        patch(&mut doc, &p).unwrap();
545        assert_eq!(doc, platform_value!([1, 2, 3]));
546    }
547
548    // ---------------------------------------------------------------
549    // remove operation
550    // ---------------------------------------------------------------
551
552    #[test]
553    fn remove_from_map() {
554        let mut doc = platform_value!({"a": 1, "b": 2});
555        let p: Patch = from_value(platform_value!([
556            { "op": "remove", "path": "/a" }
557        ]))
558        .unwrap();
559        patch(&mut doc, &p).unwrap();
560        assert_eq!(doc.pointer("/a"), None);
561        assert_eq!(doc.pointer("/b"), Some(&platform_value!(2)));
562    }
563
564    #[test]
565    fn remove_from_array_by_index() {
566        let mut doc = platform_value!({"arr": [1, 2, 3]});
567        let p: Patch = from_value(platform_value!([
568            { "op": "remove", "path": "/arr/1" }
569        ]))
570        .unwrap();
571        patch(&mut doc, &p).unwrap();
572        assert_eq!(doc, platform_value!({"arr": [1, 3]}));
573    }
574
575    #[test]
576    fn remove_missing_key_errors() {
577        let mut doc = platform_value!({"a": 1});
578        let p: Patch = from_value(platform_value!([
579            { "op": "remove", "path": "/nonexistent" }
580        ]))
581        .unwrap();
582        let err = patch(&mut doc, &p).unwrap_err();
583        assert!(matches!(err.kind, PatchErrorKind::InvalidPointer));
584    }
585
586    #[test]
587    fn remove_invalid_array_index_errors() {
588        let mut doc = platform_value!({"arr": [1]});
589        let p: Patch = from_value(platform_value!([
590            { "op": "remove", "path": "/arr/5" }
591        ]))
592        .unwrap();
593        let err = patch(&mut doc, &p).unwrap_err();
594        assert!(matches!(err.kind, PatchErrorKind::InvalidPointer));
595    }
596
597    // ---------------------------------------------------------------
598    // replace operation
599    // ---------------------------------------------------------------
600
601    #[test]
602    fn replace_existing_key() {
603        let mut doc = platform_value!({"a": 1});
604        let p: Patch = from_value(platform_value!([
605            { "op": "replace", "path": "/a", "value": 99 }
606        ]))
607        .unwrap();
608        patch(&mut doc, &p).unwrap();
609        assert_eq!(doc, platform_value!({"a": 99}));
610    }
611
612    #[test]
613    fn replace_missing_key_errors() {
614        let mut doc = platform_value!({"a": 1});
615        let p: Patch = from_value(platform_value!([
616            { "op": "replace", "path": "/b", "value": 2 }
617        ]))
618        .unwrap();
619        let err = patch(&mut doc, &p).unwrap_err();
620        assert!(matches!(err.kind, PatchErrorKind::InvalidPointer));
621    }
622
623    #[test]
624    fn replace_root_document() {
625        let mut doc = platform_value!({"a": 1});
626        let p: Patch = from_value(platform_value!([
627            { "op": "replace", "path": "", "value": [1, 2, 3] }
628        ]))
629        .unwrap();
630        patch(&mut doc, &p).unwrap();
631        assert_eq!(doc, platform_value!([1, 2, 3]));
632    }
633
634    // ---------------------------------------------------------------
635    // move operation
636    // ---------------------------------------------------------------
637
638    #[test]
639    fn move_between_map_keys() {
640        let mut doc = platform_value!({"a": 1, "b": 2});
641        let p: Patch = from_value(platform_value!([
642            { "op": "move", "from": "/a", "path": "/c" }
643        ]))
644        .unwrap();
645        patch(&mut doc, &p).unwrap();
646        assert_eq!(doc.pointer("/a"), None);
647        assert_eq!(doc.pointer("/c"), Some(&platform_value!(1)));
648        assert_eq!(doc.pointer("/b"), Some(&platform_value!(2)));
649    }
650
651    #[test]
652    fn move_inside_self_errors() {
653        let mut doc = platform_value!({"a": {"b": 1}});
654        let p: Patch = from_value(platform_value!([
655            { "op": "move", "from": "/a", "path": "/a/b/c" }
656        ]))
657        .unwrap();
658        let err = patch(&mut doc, &p).unwrap_err();
659        assert!(matches!(err.kind, PatchErrorKind::CannotMoveInsideItself));
660    }
661
662    #[test]
663    fn move_from_invalid_path_errors() {
664        let mut doc = platform_value!({"a": 1});
665        let p: Patch = from_value(platform_value!([
666            { "op": "move", "from": "/nonexistent", "path": "/b" }
667        ]))
668        .unwrap();
669        let err = patch(&mut doc, &p).unwrap_err();
670        assert!(matches!(err.kind, PatchErrorKind::InvalidFromPointer));
671    }
672
673    // ---------------------------------------------------------------
674    // copy operation
675    // ---------------------------------------------------------------
676
677    #[test]
678    fn copy_between_map_keys() {
679        let mut doc = platform_value!({"a": 1});
680        let p: Patch = from_value(platform_value!([
681            { "op": "copy", "from": "/a", "path": "/b" }
682        ]))
683        .unwrap();
684        patch(&mut doc, &p).unwrap();
685        assert_eq!(doc.pointer("/a"), Some(&platform_value!(1)));
686        assert_eq!(doc.pointer("/b"), Some(&platform_value!(1)));
687    }
688
689    #[test]
690    fn copy_from_invalid_path_errors() {
691        let mut doc = platform_value!({"a": 1});
692        let p: Patch = from_value(platform_value!([
693            { "op": "copy", "from": "/missing", "path": "/b" }
694        ]))
695        .unwrap();
696        let err = patch(&mut doc, &p).unwrap_err();
697        assert!(matches!(err.kind, PatchErrorKind::InvalidFromPointer));
698    }
699
700    #[test]
701    fn copy_nested_value() {
702        let mut doc = platform_value!({"a": {"x": 10}});
703        let p: Patch = from_value(platform_value!([
704            { "op": "copy", "from": "/a", "path": "/b" }
705        ]))
706        .unwrap();
707        patch(&mut doc, &p).unwrap();
708        assert_eq!(doc.pointer("/b/x"), Some(&platform_value!(10)));
709    }
710
711    // ---------------------------------------------------------------
712    // test operation
713    // ---------------------------------------------------------------
714
715    #[test]
716    fn test_matching_value_succeeds() {
717        let mut doc = platform_value!({"a": "hello"});
718        let p: Patch = from_value(platform_value!([
719            { "op": "test", "path": "/a", "value": "hello" }
720        ]))
721        .unwrap();
722        patch(&mut doc, &p).unwrap();
723    }
724
725    #[test]
726    fn test_mismatched_value_fails() {
727        let mut doc = platform_value!({"a": "hello"});
728        let p: Patch = from_value(platform_value!([
729            { "op": "test", "path": "/a", "value": "world" }
730        ]))
731        .unwrap();
732        let err = patch(&mut doc, &p).unwrap_err();
733        assert!(matches!(err.kind, PatchErrorKind::TestFailed));
734    }
735
736    #[test]
737    fn test_missing_path_errors() {
738        let mut doc = platform_value!({"a": 1});
739        let p: Patch = from_value(platform_value!([
740            { "op": "test", "path": "/nope", "value": 1 }
741        ]))
742        .unwrap();
743        let err = patch(&mut doc, &p).unwrap_err();
744        assert!(matches!(err.kind, PatchErrorKind::InvalidPointer));
745    }
746
747    // ---------------------------------------------------------------
748    // apply_patches: multi-operation and rollback
749    // ---------------------------------------------------------------
750
751    #[test]
752    fn apply_patches_multi_operation() {
753        let mut doc = platform_value!({"a": 1});
754        let p: Patch = from_value(platform_value!([
755            { "op": "add", "path": "/b", "value": 2 },
756            { "op": "replace", "path": "/a", "value": 10 },
757            { "op": "remove", "path": "/b" }
758        ]))
759        .unwrap();
760        patch(&mut doc, &p).unwrap();
761        assert_eq!(doc, platform_value!({"a": 10}));
762    }
763
764    #[test]
765    fn apply_patches_rollback_add_new_map_key_on_failure() {
766        // Known limitation: map rollback for add-new-key does not fully
767        // restore the original because remove() on a ValueMap uses
768        // position-based lookup that may not find the appended entry.
769        // This test documents the current (broken) behavior.
770        let mut doc = platform_value!({"a": 1});
771        let p: Patch = from_value(platform_value!([
772            { "op": "add", "path": "/b", "value": 2 },
773            { "op": "test", "path": "/a", "value": 999 }
774        ]))
775        .unwrap();
776        // Patch fails (test op doesn't match), rollback is attempted
777        assert!(patch(&mut doc, &p).is_err());
778        // The key "b" should have been removed by rollback but may remain
779        // due to the ValueMap append-only behavior.
780    }
781
782    #[test]
783    fn apply_patches_rollback_add_array_on_failure() {
784        // Array rollback works correctly.
785        let mut doc = platform_value!([1, 2, 3]);
786        let original = doc.clone();
787        let p: Patch = from_value(platform_value!([
788            { "op": "add", "path": "/1", "value": 99 },
789            { "op": "test", "path": "/0", "value": 999 }
790        ]))
791        .unwrap();
792        assert!(patch(&mut doc, &p).is_err());
793        assert_eq!(doc, original);
794    }
795
796    #[test]
797    fn apply_patches_rollback_replace_on_failure() {
798        let mut doc = platform_value!({"a": 1, "b": 2});
799        let original = doc.clone();
800        let p: Patch = from_value(platform_value!([
801            { "op": "replace", "path": "/a", "value": 100 },
802            { "op": "test", "path": "/b", "value": 999 }
803        ]))
804        .unwrap();
805        assert!(patch(&mut doc, &p).is_err());
806        assert_eq!(doc, original);
807    }
808
809    #[test]
810    fn apply_patches_rollback_remove_array_on_failure() {
811        let mut doc = platform_value!([1, 2, 3]);
812        let original = doc.clone();
813        let p: Patch = from_value(platform_value!([
814            { "op": "remove", "path": "/1" },
815            { "op": "test", "path": "/0", "value": 999 }
816        ]))
817        .unwrap();
818        assert!(patch(&mut doc, &p).is_err());
819        assert_eq!(doc, original);
820    }
821
822    #[test]
823    fn apply_patches_rollback_copy_array_on_failure() {
824        let mut doc = platform_value!({"items": [10, 20]});
825        let original = doc.clone();
826        let p: Patch = from_value(platform_value!([
827            { "op": "copy", "from": "/items/0", "path": "/items/-" },
828            { "op": "test", "path": "/items/0", "value": 999 }
829        ]))
830        .unwrap();
831        assert!(patch(&mut doc, &p).is_err());
832        assert_eq!(doc, original);
833    }
834
835    #[test]
836    fn apply_patches_empty_patch_list() {
837        let mut doc = platform_value!({"a": 1});
838        let p = Patch(vec![]);
839        patch(&mut doc, &p).unwrap();
840        assert_eq!(doc, platform_value!({"a": 1}));
841    }
842
843    // ---------------------------------------------------------------
844    // merge
845    // ---------------------------------------------------------------
846
847    #[test]
848    fn merge_recursive_map() {
849        let mut doc = platform_value!({
850            "a": { "b": 1, "c": 2 }
851        });
852        let p = platform_value!({
853            "a": { "b": 10, "d": 3 }
854        });
855        merge(&mut doc, &p);
856        assert_eq!(doc.pointer("/a/b"), Some(&platform_value!(10)));
857        assert_eq!(doc.pointer("/a/c"), Some(&platform_value!(2)));
858        assert_eq!(doc.pointer("/a/d"), Some(&platform_value!(3)));
859    }
860
861    #[test]
862    fn merge_null_removes_key() {
863        let mut doc = platform_value!({"a": 1, "b": 2});
864        let p = platform_value!({"a": null});
865        merge(&mut doc, &p);
866        assert_eq!(doc.pointer("/a"), None);
867        assert_eq!(doc.pointer("/b"), Some(&platform_value!(2)));
868    }
869
870    #[test]
871    fn merge_non_map_patch_replaces_entire_document() {
872        let mut doc = platform_value!({"a": 1});
873        let p = platform_value!("replaced");
874        merge(&mut doc, &p);
875        assert_eq!(doc, platform_value!("replaced"));
876    }
877
878    #[test]
879    fn merge_into_non_map_doc_creates_map() {
880        let mut doc = platform_value!("not a map");
881        let p = platform_value!({"x": 1});
882        merge(&mut doc, &p);
883        assert_eq!(doc.pointer("/x"), Some(&platform_value!(1)));
884    }
885
886    #[test]
887    fn merge_adds_new_keys() {
888        let mut doc = platform_value!({"a": 1});
889        let p = platform_value!({"b": 2});
890        merge(&mut doc, &p);
891        assert_eq!(doc.pointer("/a"), Some(&platform_value!(1)));
892        assert_eq!(doc.pointer("/b"), Some(&platform_value!(2)));
893    }
894
895    #[test]
896    fn merge_replaces_array_entirely() {
897        let mut doc = platform_value!({"tags": [1, 2, 3]});
898        let p = platform_value!({"tags": [4]});
899        merge(&mut doc, &p);
900        assert_eq!(doc.pointer("/tags"), Some(&platform_value!([4])));
901    }
902
903    // ---------------------------------------------------------------
904    // parse_index
905    // ---------------------------------------------------------------
906
907    #[test]
908    fn parse_index_valid() {
909        assert_eq!(parse_index("0", 5).unwrap(), 0);
910        assert_eq!(parse_index("3", 5).unwrap(), 3);
911        assert_eq!(parse_index("4", 5).unwrap(), 4);
912    }
913
914    #[test]
915    fn parse_index_leading_zero_errors() {
916        assert!(matches!(
917            parse_index("01", 5),
918            Err(PatchErrorKind::InvalidPointer)
919        ));
920    }
921
922    #[test]
923    fn parse_index_leading_plus_errors() {
924        assert!(matches!(
925            parse_index("+1", 5),
926            Err(PatchErrorKind::InvalidPointer)
927        ));
928    }
929
930    #[test]
931    fn parse_index_out_of_bounds_errors() {
932        assert!(matches!(
933            parse_index("5", 5),
934            Err(PatchErrorKind::InvalidPointer)
935        ));
936    }
937
938    #[test]
939    fn parse_index_non_numeric_errors() {
940        assert!(matches!(
941            parse_index("abc", 5),
942            Err(PatchErrorKind::InvalidPointer)
943        ));
944    }
945
946    #[test]
947    fn parse_index_single_zero_valid() {
948        assert_eq!(parse_index("0", 1).unwrap(), 0);
949    }
950
951    // ---------------------------------------------------------------
952    // unescape
953    // ---------------------------------------------------------------
954
955    #[test]
956    fn unescape_tilde_zero_becomes_tilde() {
957        assert_eq!(unescape("a~0b"), "a~b");
958    }
959
960    #[test]
961    fn unescape_tilde_one_becomes_slash() {
962        assert_eq!(unescape("a~1b"), "a/b");
963    }
964
965    #[test]
966    fn unescape_both_sequences() {
967        assert_eq!(unescape("~0~1"), "~/");
968    }
969
970    #[test]
971    fn unescape_no_tilde_borrows() {
972        let result = unescape("plain");
973        assert!(matches!(result, Cow::Borrowed(_)));
974        assert_eq!(result, "plain");
975    }
976
977    #[test]
978    fn unescape_with_tilde_returns_owned() {
979        let result = unescape("a~0b");
980        assert!(matches!(result, Cow::Owned(_)));
981    }
982
983    // ---------------------------------------------------------------
984    // patch error reporting
985    // ---------------------------------------------------------------
986
987    #[test]
988    fn patch_error_reports_correct_operation_index() {
989        let mut doc = platform_value!({"a": 1});
990        let p: Patch = from_value(platform_value!([
991            { "op": "add", "path": "/b", "value": 2 },
992            { "op": "remove", "path": "/nonexistent" }
993        ]))
994        .unwrap();
995        let err = patch(&mut doc, &p).unwrap_err();
996        assert_eq!(err.operation, 1);
997        assert_eq!(err.path, "/nonexistent");
998    }
999
1000    // ---------------------------------------------------------------
1001    // split_pointer
1002    // ---------------------------------------------------------------
1003
1004    #[test]
1005    fn split_pointer_valid() {
1006        let (parent, last) = split_pointer("/a/b").unwrap();
1007        assert_eq!(parent, "/a");
1008        assert_eq!(last, "b");
1009    }
1010
1011    #[test]
1012    fn split_pointer_root_child() {
1013        let (parent, last) = split_pointer("/x").unwrap();
1014        assert_eq!(parent, "");
1015        assert_eq!(last, "x");
1016    }
1017
1018    #[test]
1019    fn split_pointer_no_slash_errors() {
1020        assert!(split_pointer("noslash").is_err());
1021    }
1022
1023    // ---------------------------------------------------------------
1024    // add: error on invalid parent
1025    // ---------------------------------------------------------------
1026
1027    #[test]
1028    fn add_to_scalar_parent_errors() {
1029        let mut doc = platform_value!({"a": 42});
1030        let p: Patch = from_value(platform_value!([
1031            { "op": "add", "path": "/a/b", "value": 1 }
1032        ]))
1033        .unwrap();
1034        let err = patch(&mut doc, &p).unwrap_err();
1035        assert!(matches!(err.kind, PatchErrorKind::InvalidPointer));
1036    }
1037}