surrealdb_expr/val/value/
diff.rs1use crate::expr::operation::Operation;
2use crate::val::{Strand, Value};
3
4impl Value {
5 pub fn diff(&self, val: &Value) -> Vec<Operation> {
6 let mut res = Vec::new();
7 let mut path = Vec::new();
8
9 self.diff_rec(val, &mut path, &mut res);
10
11 res
12 }
13
14 fn diff_rec(&self, val: &Value, path: &mut Vec<Strand>, ops: &mut Vec<Operation>) {
15 match (self, val) {
16 (Value::Object(a), Value::Object(b)) if a != b => {
17 for (key, _) in a.iter() {
19 if !b.contains_key(key) {
20 let mut path = path.clone();
21 path.push(key.clone());
22 ops.push(Operation::Remove {
23 path,
24 });
25 }
26 }
27 for (key, val) in b.iter() {
29 match a.get(key) {
30 None => {
31 let mut path = path.clone();
32 path.push(key.clone());
33 ops.push(Operation::Add {
34 path,
35 value: val.clone(),
36 });
37 }
38 Some(old) => {
39 path.push(key.clone());
40 old.diff_rec(val, path, ops);
41 path.pop();
42 }
43 }
44 }
45 }
46 (Value::Array(a), Value::Array(b)) if a != b => {
47 let min_len = a.len().min(b.len());
48 for n in 0..min_len {
49 path.push(n.to_string().into());
50 a[n].diff_rec(&b[n], path, ops);
51 path.pop();
52 }
53 for n in min_len..b.len() {
54 let mut path = path.clone();
55 path.push(n.to_string().into());
56 ops.push(Operation::Add {
57 path,
58 value: b[n].clone(),
59 })
60 }
61 for n in (min_len..a.len()).rev() {
66 let mut path = path.clone();
67 path.push(n.to_string().into());
68 ops.push(Operation::Remove {
69 path,
70 })
71 }
72 }
73 (Value::String(a), Value::String(b)) if a != b => ops.push(Operation::Change {
74 path: path.clone(),
75 value: {
76 let dmp = dmp::new();
77 let pch = dmp.patch_make1(a, b);
78 let txt = dmp.patch_to_text(&pch);
79 txt.into()
80 },
81 }),
82 (a, b) if a != b => ops.push(Operation::Replace {
83 path: path.clone(),
84 value: val.clone(),
85 }),
86 (_, _) => (),
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93
94 use super::*;
95 use crate::syn;
96
97 macro_rules! parse_val {
98 ($input:expr) => {
99 crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
100 };
101 }
102
103 #[test]
104 fn diff_none() {
105 let old = parse_val!("{ test: true, text: 'text', other: { something: true } }");
106 let now = parse_val!("{ test: true, text: 'text', other: { something: true } }");
107 let res = parse_val!("[]");
108 let res = Operation::value_to_operations(res).unwrap();
109 assert_eq!(res, old.diff(&now));
110 }
111
112 #[test]
113 fn diff_add() {
114 let old = parse_val!("{ test: true }");
115 let now = parse_val!("{ test: true, other: 'test' }");
116 let res = parse_val!("[{ op: 'add', path: '/other', value: 'test' }]");
117 let res = Operation::value_to_operations(res).unwrap();
118 assert_eq!(res, old.diff(&now));
119 }
120
121 #[test]
122 fn diff_remove() {
123 let old = parse_val!("{ test: true, other: 'test' }");
124 let now = parse_val!("{ test: true }");
125 let res = parse_val!("[{ op: 'remove', path: '/other' }]");
126 let res = Operation::value_to_operations(res).unwrap();
127 assert_eq!(res, old.diff(&now));
128 }
129
130 #[test]
131 fn diff_add_array() {
132 let old = parse_val!("{ test: [1,2,3] }");
133 let now = parse_val!("{ test: [1,2,3,4] }");
134 let res = parse_val!("[{ op: 'add', path: '/test/3', value: 4 }]");
135 let res = Operation::value_to_operations(res).unwrap();
136 assert_eq!(res, old.diff(&now));
137 }
138
139 #[test]
140 fn diff_replace_embedded() {
141 let old = parse_val!("{ test: { other: 'test' } }");
142 let now = parse_val!("{ test: { other: false } }");
143 let res = parse_val!("[{ op: 'replace', path: '/test/other', value: false }]");
144 let res = Operation::value_to_operations(res).unwrap();
145 assert_eq!(res, old.diff(&now));
146 }
147
148 #[test]
149 fn diff_change_text() {
150 let old = parse_val!("{ test: { other: 'test' } }");
151 let now = parse_val!("{ test: { other: 'text' } }");
152 let res = parse_val!(
153 "[{ op: 'change', path: '/test/other', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]"
154 );
155 let res = Operation::value_to_operations(res).unwrap();
156 assert_eq!(res, old.diff(&now));
157 }
158
159 #[test]
160 fn diff_change_text_root() {
161 let old = parse_val!("'test'");
164 let now = parse_val!("'text'");
165 let ops = old.diff(&now);
166 assert_eq!(ops.len(), 1);
167 match &ops[0] {
168 Operation::Change {
169 path,
170 value: _,
171 } => {
172 assert!(path.is_empty(), "expected empty path, got {path:?}");
173 }
174 other => panic!("expected Operation::Change, got {other:?}"),
175 }
176 let patch = Operation::operations_to_value(ops);
178 let expected =
179 parse_val!("[{ op: 'change', path: '', value: '@@ -1,4 +1,4 @@\n te\n-s\n+x\n t\n' }]");
180 assert_eq!(patch, expected);
181 }
182
183 #[test]
184 fn diff_replace_root() {
185 let old = parse_val!("1");
188 let now = parse_val!("2");
189 let ops = old.diff(&now);
190 assert_eq!(ops.len(), 1);
191 match &ops[0] {
192 Operation::Replace {
193 path,
194 value: _,
195 } => {
196 assert!(path.is_empty(), "expected empty path, got {path:?}");
197 }
198 other => panic!("expected Operation::Replace, got {other:?}"),
199 }
200 let patch = Operation::operations_to_value(ops);
201 let expected = parse_val!("[{ op: 'replace', path: '', value: 2 }]");
202 assert_eq!(patch, expected);
203 }
204}