1use serde::{Deserialize, Serialize};
31
32use crate::core::{Fields, Value};
33use crate::repr::Repr;
34
35#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37pub enum Step {
38 Field(String),
40 Index(u32),
42 Key(Repr),
44}
45
46pub type Path = Vec<Step>;
48
49#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
51pub enum Op {
52 Set { path: Path, value: Repr },
55 Insert { path: Path, index: u32, value: Repr },
57 Remove { path: Path, index: u32 },
59 Put { path: Path, key: Repr, value: Repr },
61 Drop { path: Path, key: Repr },
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
67pub struct BadPatch {
68 pub why: String,
69}
70
71impl std::fmt::Display for BadPatch {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 f.write_str(&self.why)
74 }
75}
76
77impl std::error::Error for BadPatch {}
78
79fn bad(why: impl Into<String>) -> BadPatch {
80 BadPatch { why: why.into() }
81}
82
83pub fn diff(old: &Value, new: &Value) -> Vec<Op> {
88 let mut ops = Vec::new();
89 walk(&mut Vec::new(), old, new, &mut ops);
90 ops
91}
92
93fn walk(path: &mut Path, old: &Value, new: &Value, ops: &mut Vec<Op>) {
94 if old == new {
95 return;
96 }
97 match (old, new) {
98 (Value::Data(a), Value::Data(b)) if a.ty == b.ty && a.variant == b.variant => {
101 for (name, av) in a.fields.iter() {
102 let Some(bv) = b.fields.get(name) else {
103 set(path, new, ops);
105 return;
106 };
107 path.push(Step::Field(name.to_string()));
108 walk(path, av, bv, ops);
109 path.pop();
110 }
111 }
112 (Value::Map(a), Value::Map(b)) => {
113 let mut left = a.iter().peekable();
115 let mut right = b.iter().peekable();
116 loop {
117 match (left.peek(), right.peek()) {
118 (None, None) => break,
119 (Some((k, _)), None) => {
120 drop_key(path, k, ops);
121 left.next();
122 }
123 (None, Some((k, v))) => {
124 put(path, k, v, ops);
125 right.next();
126 }
127 (Some((lk, lv)), Some((rk, rv))) => match lk.cmp(rk) {
128 std::cmp::Ordering::Less => {
129 drop_key(path, lk, ops);
130 left.next();
131 }
132 std::cmp::Ordering::Greater => {
133 put(path, rk, rv, ops);
134 right.next();
135 }
136 std::cmp::Ordering::Equal => {
137 if lv != rv {
138 if let Ok(key) = Repr::of(lk) {
141 path.push(Step::Key(key));
142 walk(path, lv, rv, ops);
143 path.pop();
144 } else {
145 put(path, rk, rv, ops);
146 }
147 }
148 left.next();
149 right.next();
150 }
151 },
152 }
153 }
154 }
155 (Value::List(a), Value::List(b)) => {
156 let common = a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count();
161 let tail = a
162 .iter()
163 .rev()
164 .zip(b.iter().rev())
165 .take_while(|(x, y)| x == y)
166 .count()
167 .min(a.len() - common)
168 .min(b.len() - common);
169 if a.len() == b.len() && common + tail + 1 == a.len() {
171 let (Some(x), Some(y)) = (a.get(common), b.get(common)) else {
172 return set(path, new, ops);
173 };
174 path.push(Step::Index(common as u32));
175 walk(path, &x, &y, ops);
176 path.pop();
177 return;
178 }
179 for i in (common..a.len() - tail).rev() {
181 ops.push(Op::Remove {
182 path: path.clone(),
183 index: i as u32,
184 });
185 }
186 for (offset, v) in b.slice(common, b.len() - tail).iter().enumerate() {
187 let Ok(value) = Repr::of(&v) else {
188 return set(path, new, ops);
189 };
190 ops.push(Op::Insert {
191 path: path.clone(),
192 index: (common + offset) as u32,
193 value,
194 });
195 }
196 }
197 _ => set(path, new, ops),
198 }
199}
200
201fn set(path: &Path, new: &Value, ops: &mut Vec<Op>) {
202 if let Ok(value) = Repr::of(new) {
207 ops.push(Op::Set {
208 path: path.clone(),
209 value,
210 });
211 }
212}
213
214fn put(path: &Path, key: &Value, value: &Value, ops: &mut Vec<Op>) {
215 if let (Ok(key), Ok(value)) = (Repr::of(key), Repr::of(value)) {
216 ops.push(Op::Put {
217 path: path.clone(),
218 key,
219 value,
220 });
221 }
222}
223
224fn drop_key(path: &Path, key: &Value, ops: &mut Vec<Op>) {
225 if let Ok(key) = Repr::of(key) {
226 ops.push(Op::Drop {
227 path: path.clone(),
228 key,
229 });
230 }
231}
232
233pub fn apply(state: &Value, ops: &[Op]) -> Result<Value, BadPatch> {
240 let mut out = state.clone();
241 for op in ops {
242 out = apply_one(&out, op)?;
243 }
244 Ok(out)
245}
246
247fn apply_one(state: &Value, op: &Op) -> Result<Value, BadPatch> {
248 let (path, edit): (&Path, Edit) = match op {
249 Op::Set { path, value } => (path, Edit::Set(value.to_value())),
250 Op::Insert { path, index, value } => (path, Edit::Insert(*index, value.to_value())),
251 Op::Remove { path, index } => (path, Edit::Remove(*index)),
252 Op::Put { path, key, value } => (path, Edit::Put(key.to_value(), value.to_value())),
253 Op::Drop { path, key } => (path, Edit::Drop(key.to_value())),
254 };
255 edit_at(state, path, &edit)
256}
257
258enum Edit {
259 Set(Value),
260 Insert(u32, Value),
261 Remove(u32),
262 Put(Value, Value),
263 Drop(Value),
264}
265
266fn edit_at(state: &Value, path: &[Step], edit: &Edit) -> Result<Value, BadPatch> {
267 let Some((step, rest)) = path.split_first() else {
268 return here(state, edit);
269 };
270 match (step, state) {
271 (Step::Field(name), Value::Data(d)) => {
272 let old = d
273 .fields
274 .get(name.as_str())
275 .ok_or_else(|| bad(format!("no field `{name}` here")))?;
276 let next = edit_at(old, rest, edit)?;
277 let mut fields = Fields::new();
278 for (k, v) in d.fields.iter() {
279 fields.insert(
280 k.clone(),
281 if k.as_ref() == name.as_str() {
282 next.clone()
283 } else {
284 v.clone()
285 },
286 );
287 }
288 Ok(Value::data(d.ty.clone(), d.variant.clone(), fields))
289 }
290 (Step::Index(i), Value::List(xs)) => {
291 let i = *i as usize;
292 let old = xs
293 .get(i)
294 .ok_or_else(|| bad(format!("this list has no element {i}")))?;
295 let next = edit_at(&old, rest, edit)?;
296 let mut items = (**xs).clone();
299 items.set(i, next);
300 Ok(Value::of_seq(items))
301 }
302 (Step::Key(k), Value::Map(m)) => {
303 let key = k.to_value();
304 let old = m.get(&key).ok_or_else(|| bad("no such key here"))?;
305 let next = edit_at(old, rest, edit)?;
306 Ok(Value::Map(m.insert(key, next)))
307 }
308 (step, other) => Err(bad(format!(
309 "cannot follow {} into a {}",
310 match step {
311 Step::Field(n) => format!("`.{n}`"),
312 Step::Index(i) => format!("`[{i}]`"),
313 Step::Key(_) => "a key".to_string(),
314 },
315 other.display()
316 ))),
317 }
318}
319
320fn here(state: &Value, edit: &Edit) -> Result<Value, BadPatch> {
321 match edit {
322 Edit::Set(v) => Ok(v.clone()),
323 Edit::Insert(i, v) => {
324 let Value::List(xs) = state else {
325 return Err(bad("insert applies to a list"));
326 };
327 let i = *i as usize;
328 if i > xs.len() {
329 return Err(bad(format!(
330 "cannot insert at {i} in a list of {}",
331 xs.len()
332 )));
333 }
334 let mut items = xs.to_vec();
335 items.insert(i, v.clone());
336 Ok(Value::list(items))
337 }
338 Edit::Remove(i) => {
339 let Value::List(xs) = state else {
340 return Err(bad("remove applies to a list"));
341 };
342 let i = *i as usize;
343 if i >= xs.len() {
344 return Err(bad(format!("this list has no element {i}")));
345 }
346 let mut items = xs.to_vec();
347 items.remove(i);
348 Ok(Value::list(items))
349 }
350 Edit::Put(k, v) => {
351 let Value::Map(m) = state else {
352 return Err(bad("put applies to a map"));
353 };
354 Ok(Value::Map(m.insert(k.clone(), v.clone())))
355 }
356 Edit::Drop(k) => {
357 let Value::Map(m) = state else {
358 return Err(bad("drop applies to a map"));
359 };
360 Ok(Value::Map(m.remove(k)))
361 }
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::pmap::PMap;
369 use std::sync::Arc;
370
371 fn card(id: &str, text: &str, column: i64) -> Value {
372 Value::data(
373 Arc::from("Card"),
374 None,
375 Fields::from_iter([
376 (Arc::from("id"), Value::str_(id)),
377 (Arc::from("text"), Value::str_(text)),
378 (Arc::from("column"), Value::Int(column)),
379 ]),
380 )
381 }
382
383 fn board(cards: &[(&str, &str, i64)]) -> Value {
384 let mut m = PMap::new();
385 for (id, text, column) in cards {
386 m = m.insert(Value::str_(id), card(id, text, *column));
387 }
388 Value::data(
389 Arc::from("Board"),
390 None,
391 Fields::from_iter([(Arc::from("cards"), Value::Map(m))]),
392 )
393 }
394
395 fn round_trip(old: &Value, new: &Value) -> Vec<Op> {
398 let ops = diff(old, new);
399 assert_eq!(&apply(old, &ops).expect("applies"), new, "ops: {ops:?}");
400 ops
401 }
402
403 #[test]
404 fn an_unchanged_state_is_no_ops() {
405 assert!(round_trip(&board(&[("1", "a", 0)]), &board(&[("1", "a", 0)])).is_empty());
406 }
407
408 #[test]
409 fn a_card_moved_on_a_large_board_is_one_op() {
410 let many: Vec<(String, String, i64)> = (0..500)
411 .map(|i| (format!("{i:03}"), format!("card {i}"), 0))
412 .collect();
413 let before: Vec<(&str, &str, i64)> = many
414 .iter()
415 .map(|(a, b, c)| (a.as_str(), b.as_str(), *c))
416 .collect();
417 let mut after = before.clone();
418 after[250].2 = 1;
419
420 let ops = round_trip(&board(&before), &board(&after));
421 assert_eq!(ops.len(), 1, "{ops:?}");
422 match &ops[0] {
424 Op::Set { path, value } => {
425 assert_eq!(path.len(), 3, "{path:?}");
426 assert_eq!(*value, Repr::Int(1));
427 }
428 other => panic!("expected a set, got {other:?}"),
429 }
430 }
431
432 #[test]
433 fn an_added_card_is_one_put_and_a_dropped_card_is_one_drop() {
434 let ops = round_trip(
435 &board(&[("1", "a", 0)]),
436 &board(&[("1", "a", 0), ("2", "b", 0)]),
437 );
438 assert!(matches!(ops.as_slice(), [Op::Put { .. }]), "{ops:?}");
439
440 let ops = round_trip(
441 &board(&[("1", "a", 0), ("2", "b", 0)]),
442 &board(&[("1", "a", 0)]),
443 );
444 assert!(matches!(ops.as_slice(), [Op::Drop { .. }]), "{ops:?}");
445 }
446
447 #[test]
448 fn a_list_appends_removes_and_replaces() {
449 let list = |xs: &[i64]| Value::list(xs.iter().copied().map(Value::Int).collect());
450
451 let ops = round_trip(&list(&[1, 2, 3]), &list(&[1, 2, 3, 4]));
452 assert!(
453 matches!(ops.as_slice(), [Op::Insert { index: 3, .. }]),
454 "{ops:?}"
455 );
456
457 let ops = round_trip(&list(&[1, 2, 3]), &list(&[1, 3]));
458 assert!(
459 matches!(ops.as_slice(), [Op::Remove { index: 1, .. }]),
460 "{ops:?}"
461 );
462
463 let ops = round_trip(&list(&[1, 2, 3]), &list(&[1, 9, 3]));
464 assert!(matches!(ops.as_slice(), [Op::Set { .. }]), "{ops:?}");
465
466 round_trip(&list(&[1, 2, 3]), &list(&[]));
467 round_trip(&list(&[]), &list(&[7, 8]));
468 round_trip(&list(&[1, 2, 3]), &list(&[3, 2, 1]));
469 }
470
471 #[test]
472 fn a_patch_against_the_wrong_state_fails_rather_than_guesses() {
473 let ops = diff(&board(&[("1", "a", 0)]), &board(&[("1", "b", 0)]));
474 assert!(apply(&board(&[]), &ops).is_err());
476 }
477
478 #[test]
479 fn a_variant_change_replaces_rather_than_descends() {
480 let some = Value::data(
481 Arc::from("Option"),
482 Some(Arc::from("Some")),
483 Fields::from_iter([(Arc::from("value"), Value::Int(1))]),
484 );
485 let none = Value::data(Arc::from("Option"), Some(Arc::from("None")), Fields::new());
486 let ops = round_trip(&some, &none);
487 assert!(matches!(ops.as_slice(), [Op::Set { .. }]), "{ops:?}");
488 }
489}