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}