platform_value/patch/
diff.rs

1use crate::Value;
2use std::fmt;
3use std::fmt::Display;
4
5struct PatchDiffer {
6    path: String,
7    patch: super::Patch,
8    shift: usize,
9}
10
11impl PatchDiffer {
12    fn new() -> Self {
13        Self {
14            path: "/".to_string(),
15            patch: super::Patch(Vec::new()),
16            shift: 0,
17        }
18    }
19}
20
21impl<'a> treediff::Delegate<'a, PlatformItemKey, Value> for PatchDiffer {
22    fn push(&mut self, key: &PlatformItemKey) {
23        use std::fmt::Write;
24        if self.path.len() != 1 {
25            self.path.push('/');
26        }
27        match key {
28            PlatformItemKey::Index(idx) => write!(self.path, "{}", *idx).unwrap(),
29            PlatformItemKey::String(ref key) => append_path(&mut self.path, key),
30            PlatformItemKey::BigSignedIndex(idx) => write!(self.path, "{}", *idx).unwrap(),
31            PlatformItemKey::BigIndex(idx) => write!(self.path, "{}", *idx).unwrap(),
32            PlatformItemKey::SignedIndex(idx) => write!(self.path, "{}", *idx).unwrap(),
33            PlatformItemKey::Bytes(bytes) => write!(self.path, "{}", hex::encode(bytes)).unwrap(),
34            PlatformItemKey::ArrayIndex(idx) => write!(self.path, "{}", *idx - self.shift).unwrap(),
35        }
36    }
37
38    fn pop(&mut self) {
39        let mut pos = self.path.rfind('/').unwrap_or(0);
40        if pos == 0 {
41            pos = 1;
42        }
43        self.path.truncate(pos);
44        self.shift = 0;
45    }
46
47    fn removed<'b>(&mut self, k: &'b PlatformItemKey, _v: &'a Value) {
48        let len = self.path.len();
49        self.push(k);
50        self.patch
51            .0
52            .push(super::PatchOperation::Remove(super::RemoveOperation {
53                path: self.path.clone(),
54            }));
55        // Shift indices, we are deleting array elements
56        if let PlatformItemKey::ArrayIndex(_) = k {
57            self.shift += 1;
58        }
59        self.path.truncate(len);
60    }
61
62    fn added(&mut self, k: &PlatformItemKey, v: &Value) {
63        let len = self.path.len();
64        self.push(k);
65        self.patch
66            .0
67            .push(super::PatchOperation::Add(super::AddOperation {
68                path: self.path.clone(),
69                value: v.clone(),
70            }));
71        self.path.truncate(len);
72    }
73
74    fn modified(&mut self, _old: &'a Value, new: &'a Value) {
75        self.patch
76            .0
77            .push(super::PatchOperation::Replace(super::ReplaceOperation {
78                path: self.path.clone(),
79                value: new.clone(),
80            }));
81    }
82}
83
84fn append_path(path: &mut String, key: &str) {
85    path.reserve(key.len());
86    for ch in key.chars() {
87        if ch == '~' {
88            *path += "~0";
89        } else if ch == '/' {
90            *path += "~1";
91        } else {
92            path.push(ch);
93        }
94    }
95}
96
97/// Diff two Platform Value documents and generate a Platform Value Patch (RFC 6902).
98///
99/// # Example
100/// Diff two JSONs:
101///
102/// ```rust
103/// #[macro_use]
104///
105/// use platform_value::{from_value, patch, platform_value};
106///
107/// # pub fn main() {
108/// use platform_value::patch::diff;
109/// let left = platform_value!({
110///   "title": "Goodbye!",
111///   "author" : {
112///     "givenName" : "John",
113///     "familyName" : "Doe"
114///   },
115///   "tags":[ "example", "sample" ],
116///   "content": "This will be unchanged"
117/// });
118///
119/// let right = platform_value!({
120///   "title": "Hello!",
121///   "author" : {
122///     "givenName" : "John"
123///   },
124///   "tags": [ "example" ],
125///   "content": "This will be unchanged",
126///   "phoneNumber": "+01-123-456-7890"
127/// });
128///
129/// let p = diff(&left, &right);
130/// assert_eq!(p, from_value(platform_value!([
131///   { "op": "remove", "path": "/author/familyName" },
132///   { "op": "remove", "path": "/tags/1" },
133///   { "op": "replace", "path": "/title", "value": "Hello!" },
134///   { "op": "add", "path": "/phoneNumber", "value": "+01-123-456-7890" },
135/// ])).unwrap());
136///
137/// let mut doc = left.clone();
138/// patch(&mut doc, &p).unwrap();
139/// assert_eq!(doc, right);
140///
141/// # }
142/// ```
143pub fn diff(left: &Value, right: &Value) -> super::Patch {
144    let mut differ = PatchDiffer::new();
145    treediff::diff(left, right, &mut differ);
146    differ.patch
147}
148
149/// A representation of all key types typical Value types will assume.
150#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
151pub enum PlatformItemKey {
152    /// A big index
153    BigSignedIndex(i128),
154    /// A big index
155    BigIndex(u128),
156    /// An array index
157    SignedIndex(i64),
158    /// An array index
159    Index(u64),
160    /// An array index
161    ArrayIndex(usize),
162    /// Bytes
163    Bytes(Vec<u8>),
164    /// A string index for mappings
165    String(String),
166}
167
168impl Display for PlatformItemKey {
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        match *self {
171            PlatformItemKey::String(ref v) => v.fmt(f),
172            PlatformItemKey::Index(ref v) => v.fmt(f),
173            PlatformItemKey::BigSignedIndex(ref v) => v.fmt(f),
174            PlatformItemKey::BigIndex(ref v) => v.fmt(f),
175            PlatformItemKey::SignedIndex(ref v) => v.fmt(f),
176            PlatformItemKey::Bytes(ref v) => hex::encode(v).fmt(f),
177            PlatformItemKey::ArrayIndex(ref v) => v.fmt(f),
178        }
179    }
180}
181
182impl From<Value> for Option<PlatformItemKey> {
183    fn from(value: Value) -> Self {
184        match value {
185            Value::U128(i) => Some(PlatformItemKey::BigIndex(i)),
186            Value::I128(i) => Some(PlatformItemKey::BigSignedIndex(i)),
187            Value::U64(i) => Some(PlatformItemKey::Index(i)),
188            Value::I64(i) => Some(PlatformItemKey::SignedIndex(i)),
189            Value::U32(i) => Some(PlatformItemKey::Index(i as u64)),
190            Value::I32(i) => Some(PlatformItemKey::SignedIndex(i as i64)),
191            Value::U16(i) => Some(PlatformItemKey::Index(i as u64)),
192            Value::I16(i) => Some(PlatformItemKey::SignedIndex(i as i64)),
193            Value::U8(i) => Some(PlatformItemKey::Index(i as u64)),
194            Value::I8(i) => Some(PlatformItemKey::SignedIndex(i as i64)),
195            Value::Bytes(bytes) => Some(PlatformItemKey::Bytes(bytes)),
196            Value::Bytes20(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
197            Value::Bytes32(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
198            Value::Bytes36(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
199            Value::EnumU8(_) => None,
200            Value::EnumString(_) => None,
201            Value::Identifier(bytes) => Some(PlatformItemKey::Bytes(bytes.into())),
202            Value::Float(_) => None,
203            Value::Text(str) => Some(PlatformItemKey::String(str)),
204            Value::Bool(_) => None,
205            Value::Null => None,
206            Value::Array(_) => None,
207            Value::Map(_) => None,
208        }
209    }
210}
211
212impl treediff::Value for Value {
213    type Key = PlatformItemKey;
214    /// The Value type itself.
215    type Item = Value;
216    /// Returns `None` if this is a scalar value, and an iterator yielding (Key, Value) pairs
217    /// otherwise. It is entirely possible for it to yield no values though.
218    #[allow(clippy::type_complexity)]
219    fn items<'a>(&'a self) -> Option<Box<dyn Iterator<Item = (Self::Key, &'a Self::Item)> + 'a>> {
220        match *self {
221            Value::Array(ref inner) => Some(Box::new(
222                inner
223                    .iter()
224                    .enumerate()
225                    .map(|(i, v)| (PlatformItemKey::ArrayIndex(i), v)),
226            )),
227            Value::Map(ref inner) => Some(Box::new(inner.iter().filter_map(|(s, v)| {
228                let key: Option<PlatformItemKey> = s.clone().into();
229                key.map(|k| (k, v))
230            }))),
231            _ => None,
232        }
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use crate::{from_value, platform_value, Value};
239
240    #[test]
241    pub fn replace_all() {
242        let left = platform_value!({"title": "Hello!"});
243        let p = super::diff(&left, &Value::Null);
244        assert_eq!(
245            p,
246            from_value(platform_value!([
247                { "op": "replace", "path": "/", "value": null },
248            ]))
249            .unwrap()
250        );
251    }
252
253    #[test]
254    pub fn add_all() {
255        let right = platform_value!({"title": "Hello!"});
256        let p = super::diff(&Value::Null, &right);
257        assert_eq!(
258            p,
259            from_value(platform_value!([
260                { "op": "replace", "path": "/", "value": { "title": "Hello!" } },
261            ]))
262            .unwrap()
263        );
264    }
265
266    #[test]
267    pub fn remove_all() {
268        let left = platform_value!(["hello", "bye"]);
269        let right = platform_value!([]);
270        let p = super::diff(&left, &right);
271        assert_eq!(
272            p,
273            from_value(platform_value!([
274                { "op": "remove", "path": "/0" },
275                { "op": "remove", "path": "/0" },
276            ]))
277            .unwrap()
278        );
279    }
280
281    #[test]
282    pub fn remove_tail() {
283        let left = platform_value!(["hello", "bye", "hi"]);
284        let right = platform_value!(["hello"]);
285        let p = super::diff(&left, &right);
286        assert_eq!(
287            p,
288            from_value(platform_value!([
289                { "op": "remove", "path": "/1" },
290                { "op": "remove", "path": "/1" },
291            ]))
292            .unwrap()
293        );
294    }
295    #[test]
296    pub fn replace_object() {
297        let left = platform_value!(["hello", "bye"]);
298        let right = platform_value!({"hello": "bye"});
299        let p = super::diff(&left, &right);
300        assert_eq!(
301            p,
302            from_value(platform_value!([
303                { "op": "add", "path": "/hello", "value": "bye" },
304                { "op": "remove", "path": "/0" },
305                { "op": "remove", "path": "/0" },
306            ]))
307            .unwrap()
308        );
309    }
310
311    #[test]
312    fn escape_json_keys() {
313        let mut left = platform_value!({
314            "/slashed/path": 1
315        });
316        let right = platform_value!({
317            "/slashed/path": 2,
318        });
319        let patch = super::diff(&left, &right);
320
321        eprintln!("{:?}", patch);
322
323        crate::patch(&mut left, &patch).unwrap();
324        assert_eq!(left, right);
325    }
326}