1use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::fmt;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum PatchError {
19 PathNotFound(String),
21 InvalidTarget(String),
23 IndexOutOfBounds {
25 path: String,
27 index: usize,
29 len: usize,
31 },
32 TestFailed {
34 path: String,
36 expected: String,
38 actual: String,
40 },
41 InvalidPath(String),
43 InvalidIndex(String),
45 CannotRemoveRoot,
47 SourceNotFound(String),
49}
50
51impl fmt::Display for PatchError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::PathNotFound(path) => write!(f, "path not found: {path}"),
55 Self::InvalidTarget(path) => write!(f, "invalid target at: {path}"),
56 Self::IndexOutOfBounds { path, index, len } => {
57 write!(f, "index {index} out of bounds (len {len}) at: {path}")
58 }
59 Self::TestFailed {
60 path,
61 expected,
62 actual,
63 } => {
64 write!(
65 f,
66 "test failed at {path}: expected {expected}, got {actual}"
67 )
68 }
69 Self::InvalidPath(msg) => write!(f, "invalid path: {msg}"),
70 Self::InvalidIndex(idx) => write!(f, "invalid array index: {idx}"),
71 Self::CannotRemoveRoot => write!(f, "cannot remove root document"),
72 Self::SourceNotFound(path) => write!(f, "source path not found: {path}"),
73 }
74 }
75}
76
77impl std::error::Error for PatchError {}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(tag = "op", rename_all = "lowercase")]
82pub enum PatchOperation {
83 Add {
85 path: String,
87 value: Value,
89 },
90 Remove {
92 path: String,
94 },
95 Replace {
97 path: String,
99 value: Value,
101 },
102 Move {
104 from: String,
106 path: String,
108 },
109 Copy {
111 from: String,
113 path: String,
115 },
116 Test {
118 path: String,
120 value: Value,
122 },
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(transparent)]
128pub struct JsonPatch {
129 pub operations: Vec<PatchOperation>,
131}
132
133impl JsonPatch {
134 #[must_use]
136 pub const fn new() -> Self {
137 Self {
138 operations: Vec::new(),
139 }
140 }
141
142 #[must_use]
144 pub const fn from_operations(operations: Vec<PatchOperation>) -> Self {
145 Self { operations }
146 }
147
148 pub fn push(&mut self, op: PatchOperation) {
150 self.operations.push(op);
151 }
152
153 #[must_use]
155 pub const fn is_empty(&self) -> bool {
156 self.operations.is_empty()
157 }
158
159 #[must_use]
161 pub const fn len(&self) -> usize {
162 self.operations.len()
163 }
164
165 pub fn apply(&self, target: &Value) -> Result<Value, PatchError> {
171 let mut result = target.clone();
172 apply_patch_mut(&mut result, self)?;
173 Ok(result)
174 }
175}
176
177impl Default for JsonPatch {
178 fn default() -> Self {
179 Self::new()
180 }
181}
182
183impl IntoIterator for JsonPatch {
184 type Item = PatchOperation;
185 type IntoIter = std::vec::IntoIter<PatchOperation>;
186
187 fn into_iter(self) -> Self::IntoIter {
188 self.operations.into_iter()
189 }
190}
191
192impl JsonPatch {
193 pub fn iter(&self) -> std::slice::Iter<'_, PatchOperation> {
195 self.operations.iter()
196 }
197}
198
199impl<'a> IntoIterator for &'a JsonPatch {
200 type Item = &'a PatchOperation;
201 type IntoIter = std::slice::Iter<'a, PatchOperation>;
202
203 fn into_iter(self) -> Self::IntoIter {
204 self.iter()
205 }
206}
207
208pub fn apply_patch(target: &Value, patch: &JsonPatch) -> Result<Value, PatchError> {
216 patch.apply(target)
217}
218
219pub fn apply_patch_mut(target: &mut Value, patch: &JsonPatch) -> Result<(), PatchError> {
226 for op in &patch.operations {
227 apply_operation(target, op)?;
228 }
229 Ok(())
230}
231
232fn apply_operation(target: &mut Value, op: &PatchOperation) -> Result<(), PatchError> {
234 match op {
235 PatchOperation::Add { path, value } => op_add(target, path, value.clone()),
236 PatchOperation::Remove { path } => op_remove(target, path),
237 PatchOperation::Replace { path, value } => op_replace(target, path, value.clone()),
238 PatchOperation::Move { from, path } => op_move(target, from, path),
239 PatchOperation::Copy { from, path } => op_copy(target, from, path),
240 PatchOperation::Test { path, value } => op_test(target, path, value),
241 }
242}
243
244fn parse_pointer(path: &str) -> Result<Vec<String>, PatchError> {
246 if path.is_empty() {
247 return Ok(vec![]);
248 }
249
250 if !path.starts_with('/') {
251 return Err(PatchError::InvalidPath(format!(
252 "JSON Pointer must start with '/': {path}"
253 )));
254 }
255
256 Ok(path[1..]
257 .split('/')
258 .map(|s| {
259 s.replace("~1", "/").replace("~0", "~")
261 })
262 .collect())
263}
264
265fn get_parent_mut<'a>(
267 target: &'a mut Value,
268 segments: &'a [String],
269) -> Result<(&'a mut Value, &'a str), PatchError> {
270 if segments.is_empty() {
271 return Err(PatchError::CannotRemoveRoot);
272 }
273
274 let (parent_segments, last) = segments.split_at(segments.len() - 1);
275 let last_key = &last[0];
276
277 let parent = navigate_to_mut(target, parent_segments)?;
278 Ok((parent, last_key))
279}
280
281fn navigate_to_mut<'a>(
283 target: &'a mut Value,
284 segments: &[String],
285) -> Result<&'a mut Value, PatchError> {
286 let mut current = target;
287
288 for segment in segments {
289 current = match current {
290 Value::Object(map) => map
291 .get_mut(segment)
292 .ok_or_else(|| PatchError::PathNotFound(segment.clone()))?,
293 Value::Array(arr) => {
294 let index = parse_array_index(segment)?;
295 let len = arr.len();
296 arr.get_mut(index)
297 .ok_or_else(|| PatchError::IndexOutOfBounds {
298 path: segment.clone(),
299 index,
300 len,
301 })?
302 }
303 _ => return Err(PatchError::InvalidTarget(segment.clone())),
304 };
305 }
306
307 Ok(current)
308}
309
310fn navigate_to<'a>(target: &'a Value, segments: &[String]) -> Result<&'a Value, PatchError> {
312 let mut current = target;
313
314 for segment in segments {
315 current = match current {
316 Value::Object(map) => map
317 .get(segment)
318 .ok_or_else(|| PatchError::PathNotFound(segment.clone()))?,
319 Value::Array(arr) => {
320 let index = parse_array_index(segment)?;
321 arr.get(index).ok_or_else(|| PatchError::IndexOutOfBounds {
322 path: segment.clone(),
323 index,
324 len: arr.len(),
325 })?
326 }
327 _ => return Err(PatchError::InvalidTarget(segment.clone())),
328 };
329 }
330
331 Ok(current)
332}
333
334fn parse_array_index(segment: &str) -> Result<usize, PatchError> {
336 if segment == "-" {
338 return Err(PatchError::InvalidIndex("-".to_string()));
339 }
340
341 segment
342 .parse::<usize>()
343 .map_err(|_| PatchError::InvalidIndex(segment.to_string()))
344}
345
346fn op_add(target: &mut Value, path: &str, value: Value) -> Result<(), PatchError> {
348 let segments = parse_pointer(path)?;
349
350 if segments.is_empty() {
351 *target = value;
353 return Ok(());
354 }
355
356 let (parent, key) = get_parent_mut(target, &segments)?;
357 let key_owned = key.to_string();
358
359 match parent {
360 Value::Object(map) => {
361 map.insert(key_owned, value);
362 Ok(())
363 }
364 Value::Array(arr) => {
365 if key == "-" {
366 arr.push(value);
368 } else {
369 let index = parse_array_index(key)?;
370 if index > arr.len() {
371 return Err(PatchError::IndexOutOfBounds {
372 path: path.to_string(),
373 index,
374 len: arr.len(),
375 });
376 }
377 arr.insert(index, value);
378 }
379 Ok(())
380 }
381 _ => Err(PatchError::InvalidTarget(path.to_string())),
382 }
383}
384
385fn op_remove(target: &mut Value, path: &str) -> Result<(), PatchError> {
387 let segments = parse_pointer(path)?;
388
389 if segments.is_empty() {
390 return Err(PatchError::CannotRemoveRoot);
391 }
392
393 let (parent, key) = get_parent_mut(target, &segments)?;
394 let key_owned = key.to_string();
395
396 match parent {
397 Value::Object(map) => {
398 if map.remove(&key_owned).is_none() {
399 return Err(PatchError::PathNotFound(path.to_string()));
400 }
401 Ok(())
402 }
403 Value::Array(arr) => {
404 let index = parse_array_index(&key_owned)?;
405 if index >= arr.len() {
406 return Err(PatchError::IndexOutOfBounds {
407 path: path.to_string(),
408 index,
409 len: arr.len(),
410 });
411 }
412 arr.remove(index);
413 Ok(())
414 }
415 _ => Err(PatchError::InvalidTarget(path.to_string())),
416 }
417}
418
419fn op_replace(target: &mut Value, path: &str, value: Value) -> Result<(), PatchError> {
421 let segments = parse_pointer(path)?;
422
423 if segments.is_empty() {
424 *target = value;
425 return Ok(());
426 }
427
428 let (parent, key) = get_parent_mut(target, &segments)?;
429 let key_owned = key.to_string();
430
431 match parent {
432 Value::Object(map) => {
433 if !map.contains_key(&key_owned) {
434 return Err(PatchError::PathNotFound(path.to_string()));
435 }
436 map.insert(key_owned, value);
437 Ok(())
438 }
439 Value::Array(arr) => {
440 let index = parse_array_index(&key_owned)?;
441 if index >= arr.len() {
442 return Err(PatchError::IndexOutOfBounds {
443 path: path.to_string(),
444 index,
445 len: arr.len(),
446 });
447 }
448 arr[index] = value;
449 Ok(())
450 }
451 _ => Err(PatchError::InvalidTarget(path.to_string())),
452 }
453}
454
455fn op_move(target: &mut Value, from: &str, to: &str) -> Result<(), PatchError> {
457 let segments = parse_pointer(from)?;
459 let value = navigate_to(target, &segments)?.clone();
460
461 op_remove(target, from)?;
463
464 op_add(target, to, value)
466}
467
468fn op_copy(target: &mut Value, from: &str, to: &str) -> Result<(), PatchError> {
470 let segments = parse_pointer(from)?;
472 let value = navigate_to(target, &segments)
473 .map_err(|_| PatchError::SourceNotFound(from.to_string()))?
474 .clone();
475
476 op_add(target, to, value)
478}
479
480fn op_test(target: &Value, path: &str, expected: &Value) -> Result<(), PatchError> {
482 let segments = parse_pointer(path)?;
483 let actual = navigate_to(target, &segments)?;
484
485 if actual != expected {
486 return Err(PatchError::TestFailed {
487 path: path.to_string(),
488 expected: expected.to_string(),
489 actual: actual.to_string(),
490 });
491 }
492
493 Ok(())
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use serde_json::json;
500
501 #[test]
502 fn test_add_to_object() {
503 let mut doc = json!({"foo": "bar"});
504 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
505 path: "/baz".to_string(),
506 value: json!("qux"),
507 }]);
508
509 apply_patch_mut(&mut doc, &patch).unwrap();
510 assert_eq!(doc, json!({"foo": "bar", "baz": "qux"}));
511 }
512
513 #[test]
514 fn test_add_to_array() {
515 let mut doc = json!({"foo": ["bar", "baz"]});
516 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
517 path: "/foo/1".to_string(),
518 value: json!("qux"),
519 }]);
520
521 apply_patch_mut(&mut doc, &patch).unwrap();
522 assert_eq!(doc, json!({"foo": ["bar", "qux", "baz"]}));
523 }
524
525 #[test]
526 fn test_add_to_array_end() {
527 let mut doc = json!({"foo": ["bar"]});
528 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
529 path: "/foo/-".to_string(),
530 value: json!("qux"),
531 }]);
532
533 apply_patch_mut(&mut doc, &patch).unwrap();
534 assert_eq!(doc, json!({"foo": ["bar", "qux"]}));
535 }
536
537 #[test]
538 fn test_remove_from_object() {
539 let mut doc = json!({"foo": "bar", "baz": "qux"});
540 let patch = JsonPatch::from_operations(vec![PatchOperation::Remove {
541 path: "/baz".to_string(),
542 }]);
543
544 apply_patch_mut(&mut doc, &patch).unwrap();
545 assert_eq!(doc, json!({"foo": "bar"}));
546 }
547
548 #[test]
549 fn test_remove_from_array() {
550 let mut doc = json!({"foo": ["bar", "qux", "baz"]});
551 let patch = JsonPatch::from_operations(vec![PatchOperation::Remove {
552 path: "/foo/1".to_string(),
553 }]);
554
555 apply_patch_mut(&mut doc, &patch).unwrap();
556 assert_eq!(doc, json!({"foo": ["bar", "baz"]}));
557 }
558
559 #[test]
560 fn test_replace_value() {
561 let mut doc = json!({"foo": "bar"});
562 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
563 path: "/foo".to_string(),
564 value: json!("baz"),
565 }]);
566
567 apply_patch_mut(&mut doc, &patch).unwrap();
568 assert_eq!(doc, json!({"foo": "baz"}));
569 }
570
571 #[test]
572 fn test_move_value() {
573 let mut doc = json!({"foo": {"bar": "baz"}, "qux": {"corge": "grault"}});
574 let patch = JsonPatch::from_operations(vec![PatchOperation::Move {
575 from: "/foo/bar".to_string(),
576 path: "/qux/thud".to_string(),
577 }]);
578
579 apply_patch_mut(&mut doc, &patch).unwrap();
580 assert_eq!(
581 doc,
582 json!({"foo": {}, "qux": {"corge": "grault", "thud": "baz"}})
583 );
584 }
585
586 #[test]
587 fn test_copy_value() {
588 let mut doc = json!({"foo": {"bar": "baz"}});
589 let patch = JsonPatch::from_operations(vec![PatchOperation::Copy {
590 from: "/foo/bar".to_string(),
591 path: "/foo/qux".to_string(),
592 }]);
593
594 apply_patch_mut(&mut doc, &patch).unwrap();
595 assert_eq!(doc, json!({"foo": {"bar": "baz", "qux": "baz"}}));
596 }
597
598 #[test]
599 fn test_test_success() {
600 let doc = json!({"foo": "bar"});
601 let patch = JsonPatch::from_operations(vec![PatchOperation::Test {
602 path: "/foo".to_string(),
603 value: json!("bar"),
604 }]);
605
606 apply_patch(&doc, &patch).unwrap();
607 }
608
609 #[test]
610 fn test_test_failure() {
611 let doc = json!({"foo": "bar"});
612 let patch = JsonPatch::from_operations(vec![PatchOperation::Test {
613 path: "/foo".to_string(),
614 value: json!("baz"),
615 }]);
616
617 let result = apply_patch(&doc, &patch);
618 assert!(matches!(result, Err(PatchError::TestFailed { .. })));
619 }
620
621 #[test]
622 fn test_replace_root() {
623 let mut doc = json!({"foo": "bar"});
624 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
625 path: String::new(),
626 value: json!({"completely": "new"}),
627 }]);
628
629 apply_patch_mut(&mut doc, &patch).unwrap();
630 assert_eq!(doc, json!({"completely": "new"}));
631 }
632
633 #[test]
634 fn test_nested_path() {
635 let mut doc = json!({"foo": {"bar": {"baz": "qux"}}});
636 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
637 path: "/foo/bar/baz".to_string(),
638 value: json!("new value"),
639 }]);
640
641 apply_patch_mut(&mut doc, &patch).unwrap();
642 assert_eq!(doc, json!({"foo": {"bar": {"baz": "new value"}}}));
643 }
644
645 #[test]
646 fn test_path_with_special_chars() {
647 let mut doc = json!({"foo/bar": "baz"});
648 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
649 path: "/foo~1bar".to_string(), value: json!("qux"),
651 }]);
652
653 apply_patch_mut(&mut doc, &patch).unwrap();
654 assert_eq!(doc, json!({"foo/bar": "qux"}));
655 }
656
657 #[test]
658 fn test_path_with_tilde() {
659 let mut doc = json!({"foo~bar": "baz"});
660 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
661 path: "/foo~0bar".to_string(), value: json!("qux"),
663 }]);
664
665 apply_patch_mut(&mut doc, &patch).unwrap();
666 assert_eq!(doc, json!({"foo~bar": "qux"}));
667 }
668
669 #[test]
670 fn test_patch_serialization() {
671 let patch = JsonPatch::from_operations(vec![
672 PatchOperation::Add {
673 path: "/foo".to_string(),
674 value: json!("bar"),
675 },
676 PatchOperation::Remove {
677 path: "/baz".to_string(),
678 },
679 ]);
680
681 let json = serde_json::to_string(&patch).unwrap();
682 let parsed: JsonPatch = serde_json::from_str(&json).unwrap();
683 assert_eq!(patch, parsed);
684 }
685
686 #[test]
687 fn test_error_path_not_found() {
688 let mut doc = json!({"foo": "bar"});
689 let patch = JsonPatch::from_operations(vec![PatchOperation::Remove {
690 path: "/nonexistent".to_string(),
691 }]);
692
693 let result = apply_patch_mut(&mut doc, &patch);
694 assert!(matches!(result, Err(PatchError::PathNotFound(_))));
695 }
696
697 #[test]
698 fn test_error_index_out_of_bounds() {
699 let mut doc = json!({"arr": [1, 2, 3]});
700 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
701 path: "/arr/10".to_string(),
702 value: json!(42),
703 }]);
704
705 let result = apply_patch_mut(&mut doc, &patch);
706 assert!(matches!(result, Err(PatchError::IndexOutOfBounds { .. })));
707 }
708
709 #[test]
710 fn test_json_patch_builder() {
711 let mut patch = JsonPatch::new();
712 assert!(patch.is_empty());
713
714 patch.push(PatchOperation::Add {
715 path: "/foo".to_string(),
716 value: json!("bar"),
717 });
718 assert_eq!(patch.len(), 1);
719 assert!(!patch.is_empty());
720 }
721
722 #[test]
727 fn test_error_display_path_not_found() {
728 let err = PatchError::PathNotFound("/foo".to_string());
729 assert_eq!(err.to_string(), "path not found: /foo");
730 }
731
732 #[test]
733 fn test_error_display_invalid_target() {
734 let err = PatchError::InvalidTarget("/bar".to_string());
735 assert_eq!(err.to_string(), "invalid target at: /bar");
736 }
737
738 #[test]
739 fn test_error_display_index_out_of_bounds() {
740 let err = PatchError::IndexOutOfBounds {
741 path: "/arr".to_string(),
742 index: 10,
743 len: 3,
744 };
745 assert_eq!(err.to_string(), "index 10 out of bounds (len 3) at: /arr");
746 }
747
748 #[test]
749 fn test_error_display_test_failed() {
750 let err = PatchError::TestFailed {
751 path: "/foo".to_string(),
752 expected: "\"bar\"".to_string(),
753 actual: "\"baz\"".to_string(),
754 };
755 assert_eq!(
756 err.to_string(),
757 "test failed at /foo: expected \"bar\", got \"baz\""
758 );
759 }
760
761 #[test]
762 fn test_error_display_invalid_path() {
763 let err = PatchError::InvalidPath("no slash".to_string());
764 assert_eq!(err.to_string(), "invalid path: no slash");
765 }
766
767 #[test]
768 fn test_error_display_invalid_index() {
769 let err = PatchError::InvalidIndex("abc".to_string());
770 assert_eq!(err.to_string(), "invalid array index: abc");
771 }
772
773 #[test]
774 fn test_error_display_cannot_remove_root() {
775 let err = PatchError::CannotRemoveRoot;
776 assert_eq!(err.to_string(), "cannot remove root document");
777 }
778
779 #[test]
780 fn test_error_display_source_not_found() {
781 let err = PatchError::SourceNotFound("/src".to_string());
782 assert_eq!(err.to_string(), "source path not found: /src");
783 }
784
785 #[test]
786 fn test_error_is_std_error() {
787 let err: Box<dyn std::error::Error> = Box::new(PatchError::CannotRemoveRoot);
788 assert!(err.to_string().contains("cannot remove root"));
789 }
790
791 #[test]
796 fn test_json_patch_default() {
797 let patch = JsonPatch::default();
798 assert!(patch.is_empty());
799 }
800
801 #[test]
802 fn test_json_patch_into_iter() {
803 let patch = JsonPatch::from_operations(vec![
804 PatchOperation::Add {
805 path: "/a".to_string(),
806 value: json!(1),
807 },
808 PatchOperation::Add {
809 path: "/b".to_string(),
810 value: json!(2),
811 },
812 ]);
813
814 let ops_count = patch.into_iter().count();
815 assert_eq!(ops_count, 2);
816 }
817
818 #[test]
819 fn test_json_patch_iter_ref() {
820 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
821 path: "/x".to_string(),
822 value: json!(1),
823 }]);
824
825 for op in &patch {
826 assert!(matches!(op, PatchOperation::Add { .. }));
827 }
828 assert_eq!(patch.len(), 1);
830 }
831
832 #[test]
833 fn test_json_patch_apply() {
834 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
835 path: "/new".to_string(),
836 value: json!("value"),
837 }]);
838
839 let doc = json!({"existing": 1});
840 let result = patch.apply(&doc).unwrap();
841 assert_eq!(result, json!({"existing": 1, "new": "value"}));
842 }
843
844 #[test]
849 fn test_error_remove_root() {
850 let mut doc = json!({"foo": "bar"});
851 let patch = JsonPatch::from_operations(vec![PatchOperation::Remove {
852 path: String::new(),
853 }]);
854
855 let result = apply_patch_mut(&mut doc, &patch);
856 assert!(matches!(result, Err(PatchError::CannotRemoveRoot)));
857 }
858
859 #[test]
860 fn test_error_invalid_path_no_slash() {
861 let mut doc = json!({"foo": "bar"});
862 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
863 path: "no_slash".to_string(),
864 value: json!(1),
865 }]);
866
867 let result = apply_patch_mut(&mut doc, &patch);
868 assert!(matches!(result, Err(PatchError::InvalidPath(_))));
869 }
870
871 #[test]
872 fn test_error_invalid_array_index() {
873 let mut doc = json!({"arr": [1, 2, 3]});
874 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
875 path: "/arr/abc".to_string(),
876 value: json!(42),
877 }]);
878
879 let result = apply_patch_mut(&mut doc, &patch);
880 assert!(matches!(result, Err(PatchError::InvalidIndex(_))));
881 }
882
883 #[test]
884 fn test_error_invalid_target() {
885 let mut doc = json!({"str": "not_an_object"});
886 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
887 path: "/str/key".to_string(),
888 value: json!(1),
889 }]);
890
891 let result = apply_patch_mut(&mut doc, &patch);
892 assert!(matches!(result, Err(PatchError::InvalidTarget(_))));
893 }
894
895 #[test]
896 fn test_error_copy_source_not_found() {
897 let mut doc = json!({"foo": "bar"});
898 let patch = JsonPatch::from_operations(vec![PatchOperation::Copy {
899 from: "/nonexistent".to_string(),
900 path: "/target".to_string(),
901 }]);
902
903 let result = apply_patch_mut(&mut doc, &patch);
904 assert!(matches!(result, Err(PatchError::SourceNotFound(_))));
905 }
906
907 #[test]
908 fn test_error_add_to_array_out_of_bounds() {
909 let mut doc = json!({"arr": [1, 2]});
910 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
911 path: "/arr/10".to_string(),
912 value: json!(42),
913 }]);
914
915 let result = apply_patch_mut(&mut doc, &patch);
916 assert!(matches!(result, Err(PatchError::IndexOutOfBounds { .. })));
917 }
918
919 #[test]
920 fn test_error_remove_from_array_out_of_bounds() {
921 let mut doc = json!({"arr": [1, 2]});
922 let patch = JsonPatch::from_operations(vec![PatchOperation::Remove {
923 path: "/arr/10".to_string(),
924 }]);
925
926 let result = apply_patch_mut(&mut doc, &patch);
927 assert!(matches!(result, Err(PatchError::IndexOutOfBounds { .. })));
928 }
929
930 #[test]
931 fn test_error_replace_nonexistent_key() {
932 let mut doc = json!({"foo": "bar"});
933 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
934 path: "/nonexistent".to_string(),
935 value: json!(1),
936 }]);
937
938 let result = apply_patch_mut(&mut doc, &patch);
939 assert!(matches!(result, Err(PatchError::PathNotFound(_))));
940 }
941
942 #[test]
943 fn test_error_navigate_invalid_target() {
944 let mut doc = json!({"num": 42});
945 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
946 path: "/num/field".to_string(),
947 value: json!(1),
948 }]);
949
950 let result = apply_patch_mut(&mut doc, &patch);
951 assert!(matches!(result, Err(PatchError::InvalidTarget(_))));
952 }
953
954 #[test]
955 fn test_error_remove_from_invalid_target() {
956 let mut doc = json!({"num": 42});
957 let patch = JsonPatch::from_operations(vec![PatchOperation::Remove {
958 path: "/num/field".to_string(),
959 }]);
960
961 let result = apply_patch_mut(&mut doc, &patch);
962 assert!(matches!(result, Err(PatchError::InvalidTarget(_))));
963 }
964
965 #[test]
970 fn test_add_root() {
971 let mut doc = json!({"foo": "bar"});
972 let patch = JsonPatch::from_operations(vec![PatchOperation::Add {
973 path: String::new(),
974 value: json!([1, 2, 3]),
975 }]);
976
977 apply_patch_mut(&mut doc, &patch).unwrap();
978 assert_eq!(doc, json!([1, 2, 3]));
979 }
980
981 #[test]
982 fn test_replace_array_element() {
983 let mut doc = json!({"arr": [1, 2, 3]});
984 let patch = JsonPatch::from_operations(vec![PatchOperation::Replace {
985 path: "/arr/1".to_string(),
986 value: json!(42),
987 }]);
988
989 apply_patch_mut(&mut doc, &patch).unwrap();
990 assert_eq!(doc, json!({"arr": [1, 42, 3]}));
991 }
992
993 #[test]
994 fn test_remove_from_nested_array() {
995 let mut doc = json!({"nested": {"arr": [1, 2, 3]}});
996 let patch = JsonPatch::from_operations(vec![PatchOperation::Remove {
997 path: "/nested/arr/0".to_string(),
998 }]);
999
1000 apply_patch_mut(&mut doc, &patch).unwrap();
1001 assert_eq!(doc, json!({"nested": {"arr": [2, 3]}}));
1002 }
1003
1004 #[test]
1005 fn test_multiple_operations() {
1006 let mut doc = json!({"a": 1, "b": 2});
1007 let patch = JsonPatch::from_operations(vec![
1008 PatchOperation::Add {
1009 path: "/c".to_string(),
1010 value: json!(3),
1011 },
1012 PatchOperation::Remove {
1013 path: "/a".to_string(),
1014 },
1015 PatchOperation::Replace {
1016 path: "/b".to_string(),
1017 value: json!(20),
1018 },
1019 ]);
1020
1021 apply_patch_mut(&mut doc, &patch).unwrap();
1022 assert_eq!(doc, json!({"b": 20, "c": 3}));
1023 }
1024
1025 #[test]
1026 fn test_test_at_root() {
1027 let doc = json!({"foo": "bar"});
1028 let patch = JsonPatch::from_operations(vec![PatchOperation::Test {
1029 path: String::new(),
1030 value: json!({"foo": "bar"}),
1031 }]);
1032
1033 let result = apply_patch(&doc, &patch);
1034 assert!(result.is_ok());
1035 }
1036
1037 #[test]
1038 fn test_copy_to_nested() {
1039 let mut doc = json!({"source": "value", "target": {}});
1040 let patch = JsonPatch::from_operations(vec![PatchOperation::Copy {
1041 from: "/source".to_string(),
1042 path: "/target/copied".to_string(),
1043 }]);
1044
1045 apply_patch_mut(&mut doc, &patch).unwrap();
1046 assert_eq!(
1047 doc,
1048 json!({"source": "value", "target": {"copied": "value"}})
1049 );
1050 }
1051
1052 #[test]
1053 fn test_move_between_arrays() {
1054 let mut doc = json!({"arr1": [1, 2], "arr2": [3, 4]});
1055 let patch = JsonPatch::from_operations(vec![PatchOperation::Move {
1056 from: "/arr1/0".to_string(),
1057 path: "/arr2/-".to_string(),
1058 }]);
1059
1060 apply_patch_mut(&mut doc, &patch).unwrap();
1061 assert_eq!(doc, json!({"arr1": [2], "arr2": [3, 4, 1]}));
1062 }
1063
1064 #[test]
1069 fn test_patch_operation_serialize_all_types() {
1070 let ops = vec![
1071 PatchOperation::Add {
1072 path: "/a".to_string(),
1073 value: json!(1),
1074 },
1075 PatchOperation::Remove {
1076 path: "/b".to_string(),
1077 },
1078 PatchOperation::Replace {
1079 path: "/c".to_string(),
1080 value: json!(2),
1081 },
1082 PatchOperation::Move {
1083 from: "/d".to_string(),
1084 path: "/e".to_string(),
1085 },
1086 PatchOperation::Copy {
1087 from: "/f".to_string(),
1088 path: "/g".to_string(),
1089 },
1090 PatchOperation::Test {
1091 path: "/h".to_string(),
1092 value: json!(3),
1093 },
1094 ];
1095
1096 let patch = JsonPatch::from_operations(ops);
1097 let json_str = serde_json::to_string(&patch).unwrap();
1098
1099 let parsed: JsonPatch = serde_json::from_str(&json_str).unwrap();
1101 assert_eq!(parsed.len(), 6);
1102 }
1103
1104 #[test]
1109 fn test_error_clone() {
1110 let err = PatchError::PathNotFound("/test".to_string());
1111 let cloned = err.clone();
1112 assert_eq!(err, cloned);
1113 }
1114
1115 #[test]
1116 fn test_error_partial_eq() {
1117 let err1 = PatchError::CannotRemoveRoot;
1118 let err2 = PatchError::CannotRemoveRoot;
1119 let err3 = PatchError::PathNotFound("x".to_string());
1120
1121 assert_eq!(err1, err2);
1122 assert_ne!(err1, err3);
1123 }
1124
1125 #[test]
1126 fn test_error_debug() {
1127 let err = PatchError::IndexOutOfBounds {
1128 path: "/arr".to_string(),
1129 index: 5,
1130 len: 3,
1131 };
1132 let debug_str = format!("{err:?}");
1133 assert!(debug_str.contains("IndexOutOfBounds"));
1134 }
1135}