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 path.push(Step::Index(common as u32));
172 walk(path, &a[common], &b[common], ops);
173 path.pop();
174 return;
175 }
176 for i in (common..a.len() - tail).rev() {
178 ops.push(Op::Remove {
179 path: path.clone(),
180 index: i as u32,
181 });
182 }
183 for (offset, v) in b[common..b.len() - tail].iter().enumerate() {
184 let Ok(value) = Repr::of(v) else {
185 return set(path, new, ops);
186 };
187 ops.push(Op::Insert {
188 path: path.clone(),
189 index: (common + offset) as u32,
190 value,
191 });
192 }
193 }
194 _ => set(path, new, ops),
195 }
196}
197
198fn set(path: &Path, new: &Value, ops: &mut Vec<Op>) {
199 if let Ok(value) = Repr::of(new) {
204 ops.push(Op::Set {
205 path: path.clone(),
206 value,
207 });
208 }
209}
210
211fn put(path: &Path, key: &Value, value: &Value, ops: &mut Vec<Op>) {
212 if let (Ok(key), Ok(value)) = (Repr::of(key), Repr::of(value)) {
213 ops.push(Op::Put {
214 path: path.clone(),
215 key,
216 value,
217 });
218 }
219}
220
221fn drop_key(path: &Path, key: &Value, ops: &mut Vec<Op>) {
222 if let Ok(key) = Repr::of(key) {
223 ops.push(Op::Drop {
224 path: path.clone(),
225 key,
226 });
227 }
228}
229
230pub fn apply(state: &Value, ops: &[Op]) -> Result<Value, BadPatch> {
237 let mut out = state.clone();
238 for op in ops {
239 out = apply_one(&out, op)?;
240 }
241 Ok(out)
242}
243
244fn apply_one(state: &Value, op: &Op) -> Result<Value, BadPatch> {
245 let (path, edit): (&Path, Edit) = match op {
246 Op::Set { path, value } => (path, Edit::Set(value.to_value())),
247 Op::Insert { path, index, value } => (path, Edit::Insert(*index, value.to_value())),
248 Op::Remove { path, index } => (path, Edit::Remove(*index)),
249 Op::Put { path, key, value } => (path, Edit::Put(key.to_value(), value.to_value())),
250 Op::Drop { path, key } => (path, Edit::Drop(key.to_value())),
251 };
252 edit_at(state, path, &edit)
253}
254
255enum Edit {
256 Set(Value),
257 Insert(u32, Value),
258 Remove(u32),
259 Put(Value, Value),
260 Drop(Value),
261}
262
263fn edit_at(state: &Value, path: &[Step], edit: &Edit) -> Result<Value, BadPatch> {
264 let Some((step, rest)) = path.split_first() else {
265 return here(state, edit);
266 };
267 match (step, state) {
268 (Step::Field(name), Value::Data(d)) => {
269 let old = d
270 .fields
271 .get(name.as_str())
272 .ok_or_else(|| bad(format!("no field `{name}` here")))?;
273 let next = edit_at(old, rest, edit)?;
274 let mut fields = Fields::new();
275 for (k, v) in d.fields.iter() {
276 fields.insert(
277 k.clone(),
278 if k.as_ref() == name.as_str() {
279 next.clone()
280 } else {
281 v.clone()
282 },
283 );
284 }
285 Ok(Value::data(d.ty.clone(), d.variant.clone(), fields))
286 }
287 (Step::Index(i), Value::List(xs)) => {
288 let i = *i as usize;
289 let old = xs
290 .get(i)
291 .ok_or_else(|| bad(format!("this list has no element {i}")))?;
292 let next = edit_at(old, rest, edit)?;
293 let mut items = xs.as_ref().clone();
294 items[i] = next;
295 Ok(Value::List(std::sync::Arc::new(items)))
296 }
297 (Step::Key(k), Value::Map(m)) => {
298 let key = k.to_value();
299 let old = m.get(&key).ok_or_else(|| bad("no such key here"))?;
300 let next = edit_at(old, rest, edit)?;
301 Ok(Value::Map(m.insert(key, next)))
302 }
303 (step, other) => Err(bad(format!(
304 "cannot follow {} into a {}",
305 match step {
306 Step::Field(n) => format!("`.{n}`"),
307 Step::Index(i) => format!("`[{i}]`"),
308 Step::Key(_) => "a key".to_string(),
309 },
310 other.display()
311 ))),
312 }
313}
314
315fn here(state: &Value, edit: &Edit) -> Result<Value, BadPatch> {
316 match edit {
317 Edit::Set(v) => Ok(v.clone()),
318 Edit::Insert(i, v) => {
319 let Value::List(xs) = state else {
320 return Err(bad("insert applies to a list"));
321 };
322 let i = *i as usize;
323 if i > xs.len() {
324 return Err(bad(format!(
325 "cannot insert at {i} in a list of {}",
326 xs.len()
327 )));
328 }
329 let mut items = xs.as_ref().clone();
330 items.insert(i, v.clone());
331 Ok(Value::List(std::sync::Arc::new(items)))
332 }
333 Edit::Remove(i) => {
334 let Value::List(xs) = state else {
335 return Err(bad("remove applies to a list"));
336 };
337 let i = *i as usize;
338 if i >= xs.len() {
339 return Err(bad(format!("this list has no element {i}")));
340 }
341 let mut items = xs.as_ref().clone();
342 items.remove(i);
343 Ok(Value::List(std::sync::Arc::new(items)))
344 }
345 Edit::Put(k, v) => {
346 let Value::Map(m) = state else {
347 return Err(bad("put applies to a map"));
348 };
349 Ok(Value::Map(m.insert(k.clone(), v.clone())))
350 }
351 Edit::Drop(k) => {
352 let Value::Map(m) = state else {
353 return Err(bad("drop applies to a map"));
354 };
355 Ok(Value::Map(m.remove(k)))
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use crate::pmap::PMap;
364 use std::sync::Arc;
365
366 fn card(id: &str, text: &str, column: i64) -> Value {
367 Value::data(
368 Arc::from("Card"),
369 None,
370 Fields::from_iter([
371 (Arc::from("id"), Value::str_(id)),
372 (Arc::from("text"), Value::str_(text)),
373 (Arc::from("column"), Value::Int(column)),
374 ]),
375 )
376 }
377
378 fn board(cards: &[(&str, &str, i64)]) -> Value {
379 let mut m = PMap::new();
380 for (id, text, column) in cards {
381 m = m.insert(Value::str_(id), card(id, text, *column));
382 }
383 Value::data(
384 Arc::from("Board"),
385 None,
386 Fields::from_iter([(Arc::from("cards"), Value::Map(m))]),
387 )
388 }
389
390 fn round_trip(old: &Value, new: &Value) -> Vec<Op> {
393 let ops = diff(old, new);
394 assert_eq!(&apply(old, &ops).expect("applies"), new, "ops: {ops:?}");
395 ops
396 }
397
398 #[test]
399 fn an_unchanged_state_is_no_ops() {
400 assert!(round_trip(&board(&[("1", "a", 0)]), &board(&[("1", "a", 0)])).is_empty());
401 }
402
403 #[test]
404 fn a_card_moved_on_a_large_board_is_one_op() {
405 let many: Vec<(String, String, i64)> = (0..500)
406 .map(|i| (format!("{i:03}"), format!("card {i}"), 0))
407 .collect();
408 let before: Vec<(&str, &str, i64)> = many
409 .iter()
410 .map(|(a, b, c)| (a.as_str(), b.as_str(), *c))
411 .collect();
412 let mut after = before.clone();
413 after[250].2 = 1;
414
415 let ops = round_trip(&board(&before), &board(&after));
416 assert_eq!(ops.len(), 1, "{ops:?}");
417 match &ops[0] {
419 Op::Set { path, value } => {
420 assert_eq!(path.len(), 3, "{path:?}");
421 assert_eq!(*value, Repr::Int(1));
422 }
423 other => panic!("expected a set, got {other:?}"),
424 }
425 }
426
427 #[test]
428 fn an_added_card_is_one_put_and_a_dropped_card_is_one_drop() {
429 let ops = round_trip(
430 &board(&[("1", "a", 0)]),
431 &board(&[("1", "a", 0), ("2", "b", 0)]),
432 );
433 assert!(matches!(ops.as_slice(), [Op::Put { .. }]), "{ops:?}");
434
435 let ops = round_trip(
436 &board(&[("1", "a", 0), ("2", "b", 0)]),
437 &board(&[("1", "a", 0)]),
438 );
439 assert!(matches!(ops.as_slice(), [Op::Drop { .. }]), "{ops:?}");
440 }
441
442 #[test]
443 fn a_list_appends_removes_and_replaces() {
444 let list = |xs: &[i64]| Value::List(Arc::new(xs.iter().copied().map(Value::Int).collect()));
445
446 let ops = round_trip(&list(&[1, 2, 3]), &list(&[1, 2, 3, 4]));
447 assert!(
448 matches!(ops.as_slice(), [Op::Insert { index: 3, .. }]),
449 "{ops:?}"
450 );
451
452 let ops = round_trip(&list(&[1, 2, 3]), &list(&[1, 3]));
453 assert!(
454 matches!(ops.as_slice(), [Op::Remove { index: 1, .. }]),
455 "{ops:?}"
456 );
457
458 let ops = round_trip(&list(&[1, 2, 3]), &list(&[1, 9, 3]));
459 assert!(matches!(ops.as_slice(), [Op::Set { .. }]), "{ops:?}");
460
461 round_trip(&list(&[1, 2, 3]), &list(&[]));
462 round_trip(&list(&[]), &list(&[7, 8]));
463 round_trip(&list(&[1, 2, 3]), &list(&[3, 2, 1]));
464 }
465
466 #[test]
467 fn a_patch_against_the_wrong_state_fails_rather_than_guesses() {
468 let ops = diff(&board(&[("1", "a", 0)]), &board(&[("1", "b", 0)]));
469 assert!(apply(&board(&[]), &ops).is_err());
471 }
472
473 #[test]
474 fn a_variant_change_replaces_rather_than_descends() {
475 let some = Value::data(
476 Arc::from("Option"),
477 Some(Arc::from("Some")),
478 Fields::from_iter([(Arc::from("value"), Value::Int(1))]),
479 );
480 let none = Value::data(Arc::from("Option"), Some(Arc::from("None")), Fields::new());
481 let ops = round_trip(&some, &none);
482 assert!(matches!(ops.as_slice(), [Op::Set { .. }]), "{ops:?}");
483 }
484}