1use crate::Op;
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
27pub struct Patch {
28 ops: Vec<Op>,
30}
31
32impl Patch {
33 #[inline]
35 pub fn new() -> Self {
36 Self::default()
37 }
38
39 #[inline]
41 pub fn with_ops(ops: Vec<Op>) -> Self {
42 Self { ops }
43 }
44
45 #[inline]
47 pub fn with_op(mut self, op: Op) -> Self {
48 self.ops.push(op);
49 self
50 }
51
52 #[inline]
54 pub fn push(&mut self, op: Op) {
55 self.ops.push(op);
56 }
57
58 #[inline]
60 pub fn ops(&self) -> &[Op] {
61 &self.ops
62 }
63
64 #[inline]
66 pub fn ops_mut(&mut self) -> &mut Vec<Op> {
67 &mut self.ops
68 }
69
70 #[inline]
72 pub fn into_ops(self) -> Vec<Op> {
73 self.ops
74 }
75
76 #[inline]
78 pub fn is_empty(&self) -> bool {
79 self.ops.is_empty()
80 }
81
82 #[inline]
84 pub fn len(&self) -> usize {
85 self.ops.len()
86 }
87
88 #[inline]
90 pub fn extend(&mut self, other: Patch) {
91 self.ops.extend(other.ops);
92 }
93
94 #[inline]
96 pub fn merge(&mut self, other: Patch) {
97 self.extend(other);
98 }
99
100 #[inline]
102 pub fn clear(&mut self) {
103 self.ops.clear();
104 }
105
106 #[inline]
108 pub fn iter(&self) -> impl Iterator<Item = &Op> {
109 self.ops.iter()
110 }
111
112 pub fn canonicalize(&self) -> Patch {
143 use crate::Path;
144 use std::collections::HashSet;
145
146 if self.ops.is_empty() {
147 return Patch::new();
148 }
149
150 let mut covered_paths: HashSet<Path> = HashSet::new();
165 let mut kept_ops: Vec<Op> = Vec::new();
166
167 for op in self.ops.iter().rev() {
169 let op_path = op.path();
170
171 let is_covered = covered_paths.iter().any(|covered| {
173 if covered == op_path {
175 return true;
176 }
177 if covered.is_prefix_of(op_path) {
180 return true;
181 }
182 false
183 });
184
185 if is_covered {
186 continue;
188 }
189
190 kept_ops.push(op.clone());
192
193 match op {
195 Op::Set { path, .. } | Op::Delete { path } => {
196 covered_paths.retain(|p| !path.is_prefix_of(p));
199 covered_paths.insert(path.clone());
200 }
201 _ => {
202 }
205 }
206 }
207
208 kept_ops.reverse();
210
211 Patch::with_ops(kept_ops)
212 }
213}
214
215impl FromIterator<Op> for Patch {
216 fn from_iter<I: IntoIterator<Item = Op>>(iter: I) -> Self {
217 Self {
218 ops: iter.into_iter().collect(),
219 }
220 }
221}
222
223impl IntoIterator for Patch {
224 type Item = Op;
225 type IntoIter = std::vec::IntoIter<Op>;
226
227 fn into_iter(self) -> Self::IntoIter {
228 self.ops.into_iter()
229 }
230}
231
232impl<'a> IntoIterator for &'a Patch {
233 type Item = &'a Op;
234 type IntoIter = std::slice::Iter<'a, Op>;
235
236 fn into_iter(self) -> Self::IntoIter {
237 self.ops.iter()
238 }
239}
240
241impl Extend<Op> for Patch {
242 fn extend<I: IntoIterator<Item = Op>>(&mut self, iter: I) {
243 self.ops.extend(iter);
244 }
245}
246
247#[derive(Clone, Debug, Serialize, Deserialize)]
266pub struct TrackedPatch {
267 pub patch: Patch,
269
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub id: Option<String>,
273
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub timestamp: Option<u64>,
277
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub source: Option<String>,
281
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub description: Option<String>,
285}
286
287impl TrackedPatch {
288 #[inline]
290 pub fn new(patch: Patch) -> Self {
291 Self {
292 patch,
293 id: None,
294 timestamp: None,
295 source: None,
296 description: None,
297 }
298 }
299
300 #[inline]
302 pub fn with_id(mut self, id: impl Into<String>) -> Self {
303 self.id = Some(id.into());
304 self
305 }
306
307 #[inline]
309 pub fn with_timestamp(mut self, ts: u64) -> Self {
310 self.timestamp = Some(ts);
311 self
312 }
313
314 #[inline]
316 pub fn with_source(mut self, source: impl Into<String>) -> Self {
317 self.source = Some(source.into());
318 self
319 }
320
321 #[inline]
323 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
324 self.description = Some(desc.into());
325 self
326 }
327
328 #[inline]
330 pub fn patch(&self) -> &Patch {
331 &self.patch
332 }
333
334 #[inline]
336 pub fn into_patch(self) -> Patch {
337 self.patch
338 }
339}
340
341impl From<Patch> for TrackedPatch {
342 fn from(patch: Patch) -> Self {
343 TrackedPatch::new(patch)
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use crate::path;
351 use serde_json::json;
352
353 #[test]
354 fn test_patch_builder() {
355 let patch = Patch::new()
356 .with_op(Op::set(path!("a"), json!(1)))
357 .with_op(Op::set(path!("b"), json!(2)));
358
359 assert_eq!(patch.len(), 2);
360 }
361
362 #[test]
363 fn test_patch_extend() {
364 let mut p1 = Patch::new().with_op(Op::set(path!("a"), json!(1)));
365 let p2 = Patch::new().with_op(Op::set(path!("b"), json!(2)));
366
367 p1.extend(p2);
368 assert_eq!(p1.len(), 2);
369 }
370
371 #[test]
372 fn test_patch_serde() {
373 let patch = Patch::new()
374 .with_op(Op::set(path!("name"), json!("test")))
375 .with_op(Op::increment(path!("count"), 1i64));
376
377 let json = serde_json::to_string(&patch).unwrap();
378 let parsed: Patch = serde_json::from_str(&json).unwrap();
379 assert_eq!(patch, parsed);
380 }
381
382 #[test]
383 fn test_tracked_patch() {
384 let patch = Patch::new().with_op(Op::set(path!("x"), json!(42)));
385
386 let tracked = TrackedPatch::new(patch)
387 .with_id("test-001")
388 .with_source("test")
389 .with_timestamp(1234567890);
390
391 assert_eq!(tracked.id, Some("test-001".into()));
392 assert_eq!(tracked.source, Some("test".into()));
393 assert_eq!(tracked.timestamp, Some(1234567890));
394 }
395
396 #[test]
397 fn test_canonicalize_multiple_sets() {
398 let patch = Patch::new()
399 .with_op(Op::set(path!("name"), json!("Alice")))
400 .with_op(Op::set(path!("name"), json!("Bob")))
401 .with_op(Op::set(path!("name"), json!("Charlie")));
402
403 let canonical = patch.canonicalize();
404 assert_eq!(canonical.len(), 1);
405 assert_eq!(canonical.ops()[0], Op::set(path!("name"), json!("Charlie")));
406 }
407
408 #[test]
409 fn test_canonicalize_set_then_delete() {
410 let patch = Patch::new()
411 .with_op(Op::set(path!("x"), json!(42)))
412 .with_op(Op::delete(path!("x")));
413
414 let canonical = patch.canonicalize();
415 assert_eq!(canonical.len(), 1);
416 assert_eq!(canonical.ops()[0], Op::delete(path!("x")));
417 }
418
419 #[test]
420 fn test_canonicalize_increments_not_combined() {
421 let patch = Patch::new()
423 .with_op(Op::increment(path!("count"), 1i64))
424 .with_op(Op::increment(path!("count"), 2i64))
425 .with_op(Op::increment(path!("count"), 3i64));
426
427 let canonical = patch.canonicalize();
428 assert_eq!(canonical.len(), 3);
430
431 let doc = json!({"count": 0});
433 let result_original = crate::apply_patch(&doc, &patch).unwrap();
434 let result_canonical = crate::apply_patch(&doc, &canonical).unwrap();
435 assert_eq!(result_original, result_canonical);
436 }
437
438 #[test]
439 fn test_canonicalize_increment_decrement_not_combined() {
440 let patch = Patch::new()
442 .with_op(Op::increment(path!("count"), 10i64))
443 .with_op(Op::decrement(path!("count"), 3i64));
444
445 let canonical = patch.canonicalize();
446 assert_eq!(canonical.len(), 2);
447
448 let doc = json!({"count": 0});
450 let result_original = crate::apply_patch(&doc, &patch).unwrap();
451 let result_canonical = crate::apply_patch(&doc, &canonical).unwrap();
452 assert_eq!(result_original, result_canonical);
453 }
454
455 #[test]
456 fn test_canonicalize_merge_objects_not_combined() {
457 let patch = Patch::new()
459 .with_op(Op::merge_object(path!("user"), json!({"name": "Alice"})))
460 .with_op(Op::merge_object(path!("user"), json!({"age": 30})));
461
462 let canonical = patch.canonicalize();
463 assert_eq!(canonical.len(), 2);
464
465 let doc = json!({"user": {}});
467 let result_original = crate::apply_patch(&doc, &patch).unwrap();
468 let result_canonical = crate::apply_patch(&doc, &canonical).unwrap();
469 assert_eq!(result_original, result_canonical);
470 }
471
472 #[test]
473 fn test_canonicalize_preserves_different_paths() {
474 let patch = Patch::new()
482 .with_op(Op::set(path!("a"), json!(1))) .with_op(Op::set(path!("b"), json!(2))) .with_op(Op::set(path!("a"), json!(10))); let canonical = patch.canonicalize();
487 assert_eq!(canonical.len(), 2);
488 assert_eq!(canonical.ops()[0], Op::set(path!("b"), json!(2)));
490 assert_eq!(canonical.ops()[1], Op::set(path!("a"), json!(10)));
491 }
492
493 #[test]
494 fn test_canonicalize_array_ops_preserve_order() {
495 let patch = Patch::new()
496 .with_op(Op::append(path!("items"), json!(1)))
497 .with_op(Op::set(path!("name"), json!("test")))
498 .with_op(Op::append(path!("items"), json!(2)));
499
500 let canonical = patch.canonicalize();
501 assert_eq!(canonical.len(), 3);
503 assert_eq!(canonical.ops()[0], Op::append(path!("items"), json!(1)));
504 assert_eq!(canonical.ops()[1], Op::set(path!("name"), json!("test")));
505 assert_eq!(canonical.ops()[2], Op::append(path!("items"), json!(2)));
506 }
507
508 #[test]
509 fn test_canonicalize_preserves_operation_order() {
510 let patch = Patch::new()
512 .with_op(Op::set(path!("user"), json!({})))
513 .with_op(Op::append(path!("items"), json!(1)))
514 .with_op(Op::set(path!("user", "name"), json!("Alice")));
515
516 let canonical = patch.canonicalize();
517 assert_eq!(canonical.len(), 3);
519 assert_eq!(canonical.ops()[0], Op::set(path!("user"), json!({})));
520 assert_eq!(canonical.ops()[1], Op::append(path!("items"), json!(1)));
521 assert_eq!(
522 canonical.ops()[2],
523 Op::set(path!("user", "name"), json!("Alice"))
524 );
525 }
526
527 #[test]
528 fn test_canonicalize_interleaved_increment_set() {
529 let patch = Patch::new()
532 .with_op(Op::increment(path!("x"), 1i64))
533 .with_op(Op::set(path!("x"), json!(0)))
534 .with_op(Op::increment(path!("x"), 2i64));
535
536 let canonical = patch.canonicalize();
537
538 let doc = json!({"x": 100});
540 let result_original = crate::apply_patch(&doc, &patch).unwrap();
541 let result_canonical = crate::apply_patch(&doc, &canonical).unwrap();
542
543 assert_eq!(
544 result_original, result_canonical,
545 "canonicalize changed semantics! original={}, canonical={}",
546 result_original["x"], result_canonical["x"]
547 );
548 }
549
550 #[test]
551 fn test_canonicalize_interleaved_merge_set() {
552 let patch = Patch::new()
554 .with_op(Op::merge_object(path!("user"), json!({"a": 1})))
555 .with_op(Op::set(path!("user"), json!({})))
556 .with_op(Op::merge_object(path!("user"), json!({"b": 2})));
557
558 let canonical = patch.canonicalize();
559
560 let doc = json!({"user": {"existing": true}});
561 let result_original = crate::apply_patch(&doc, &patch).unwrap();
562 let result_canonical = crate::apply_patch(&doc, &canonical).unwrap();
563
564 assert_eq!(
565 result_original, result_canonical,
566 "canonicalize changed semantics!"
567 );
568 }
569
570 #[test]
571 fn test_canonicalize_empty_patch() {
572 let patch = Patch::new();
573 let canonical = patch.canonicalize();
574 assert!(canonical.is_empty());
575 }
576
577 #[test]
578 fn test_canonicalize_semantics_preserved() {
579 let patch = Patch::new()
581 .with_op(Op::increment(path!("value"), 1.5f64))
582 .with_op(Op::increment(path!("value"), 2.5f64));
583
584 let canonical = patch.canonicalize();
585 assert_eq!(canonical.len(), 2);
587
588 let doc = json!({"value": 0.0});
590 let result_original = crate::apply_patch(&doc, &patch).unwrap();
591 let result_canonical = crate::apply_patch(&doc, &canonical).unwrap();
592 assert_eq!(result_original, result_canonical);
593 }
594
595 #[test]
596 fn test_canonicalize_set_increment_set_correct_behavior() {
597 let patch = Patch::new()
607 .with_op(Op::set(path!("x"), json!(1)))
608 .with_op(Op::increment(path!("x"), 1i64))
609 .with_op(Op::set(path!("x"), json!(2)));
610
611 let canonical = patch.canonicalize();
612
613 let docs = vec![
615 json!({}), json!({"x": 100}), json!({"x": null}), ];
619
620 for doc in docs {
621 let result_original = crate::apply_patch(&doc, &patch).unwrap();
622 let result_canonical = crate::apply_patch(&doc, &canonical).unwrap();
623
624 assert_eq!(
625 result_original, result_canonical,
626 "canonicalize changed semantics for doc: {}",
627 doc
628 );
629
630 assert_eq!(result_original["x"], json!(2));
632 }
633
634 assert_eq!(
637 canonical.len(),
638 1,
639 "Expected 1 op after canonicalization, got: {:?}",
640 canonical.ops()
641 );
642
643 match &canonical.ops()[0] {
645 Op::Set { path, value } => {
646 assert_eq!(path, &path!("x"));
647 assert_eq!(value, &json!(2));
648 }
649 _ => panic!("Expected Set operation, got: {:?}", canonical.ops()[0]),
650 }
651 }
652}