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 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
97pub 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#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
151pub enum PlatformItemKey {
152 BigSignedIndex(i128),
154 BigIndex(u128),
156 SignedIndex(i64),
158 Index(u64),
160 ArrayIndex(usize),
162 Bytes(Vec<u8>),
164 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 type Item = Value;
216 #[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}