fionn_diff/
patch.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! JSON Patch implementation (RFC 6902).
3//!
4//! Provides operations to modify JSON documents:
5//! - `add`: Insert a value at a path
6//! - `remove`: Delete a value at a path
7//! - `replace`: Replace a value at a path
8//! - `move`: Move a value from one path to another
9//! - `copy`: Copy a value from one path to another
10//! - `test`: Verify a value equals the expected value
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::fmt;
15
16/// Error type for patch operations.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum PatchError {
19    /// Path does not exist in the document.
20    PathNotFound(String),
21    /// Cannot add to non-container (object/array).
22    InvalidTarget(String),
23    /// Array index out of bounds.
24    IndexOutOfBounds {
25        /// The path where the error occurred.
26        path: String,
27        /// The requested index.
28        index: usize,
29        /// The actual array length.
30        len: usize,
31    },
32    /// Test operation failed.
33    TestFailed {
34        /// The path where the test failed.
35        path: String,
36        /// The expected value.
37        expected: String,
38        /// The actual value found.
39        actual: String,
40    },
41    /// Invalid path format.
42    InvalidPath(String),
43    /// Invalid array index.
44    InvalidIndex(String),
45    /// Cannot remove from root.
46    CannotRemoveRoot,
47    /// Move/copy source path not found.
48    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/// A single patch operation.
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(tag = "op", rename_all = "lowercase")]
82pub enum PatchOperation {
83    /// Add a value at the specified path.
84    Add {
85        /// JSON Pointer path (RFC 6901).
86        path: String,
87        /// Value to add.
88        value: Value,
89    },
90    /// Remove the value at the specified path.
91    Remove {
92        /// JSON Pointer path.
93        path: String,
94    },
95    /// Replace the value at the specified path.
96    Replace {
97        /// JSON Pointer path.
98        path: String,
99        /// New value.
100        value: Value,
101    },
102    /// Move a value from one path to another.
103    Move {
104        /// Source path.
105        from: String,
106        /// Destination path.
107        path: String,
108    },
109    /// Copy a value from one path to another.
110    Copy {
111        /// Source path.
112        from: String,
113        /// Destination path.
114        path: String,
115    },
116    /// Test that a value equals the expected value.
117    Test {
118        /// JSON Pointer path.
119        path: String,
120        /// Expected value.
121        value: Value,
122    },
123}
124
125/// A JSON Patch document (sequence of operations).
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(transparent)]
128pub struct JsonPatch {
129    /// The operations in this patch.
130    pub operations: Vec<PatchOperation>,
131}
132
133impl JsonPatch {
134    /// Create a new empty patch.
135    #[must_use]
136    pub const fn new() -> Self {
137        Self {
138            operations: Vec::new(),
139        }
140    }
141
142    /// Create a patch from a vector of operations.
143    #[must_use]
144    pub const fn from_operations(operations: Vec<PatchOperation>) -> Self {
145        Self { operations }
146    }
147
148    /// Add an operation to the patch.
149    pub fn push(&mut self, op: PatchOperation) {
150        self.operations.push(op);
151    }
152
153    /// Check if the patch is empty.
154    #[must_use]
155    pub const fn is_empty(&self) -> bool {
156        self.operations.is_empty()
157    }
158
159    /// Get the number of operations.
160    #[must_use]
161    pub const fn len(&self) -> usize {
162        self.operations.len()
163    }
164
165    /// Apply this patch to a value, returning a new value.
166    ///
167    /// # Errors
168    ///
169    /// Returns an error if any operation fails.
170    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    /// Returns an iterator over the operations.
194    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
208/// Apply a JSON Patch to a value, returning a new value.
209///
210/// This is a convenience function that clones the input.
211///
212/// # Errors
213///
214/// Returns an error if any operation fails.
215pub fn apply_patch(target: &Value, patch: &JsonPatch) -> Result<Value, PatchError> {
216    patch.apply(target)
217}
218
219/// Apply a JSON Patch to a value in place.
220///
221/// # Errors
222///
223/// Returns an error if any operation fails. On error, the target
224/// may be partially modified.
225pub 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
232/// Apply a single patch operation.
233fn 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
244/// Parse a JSON Pointer path into segments.
245fn 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            // Unescape JSON Pointer encoding
260            s.replace("~1", "/").replace("~0", "~")
261        })
262        .collect())
263}
264
265/// Get a mutable reference to the parent of the target path.
266fn 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
281/// Navigate to a path, returning a mutable reference.
282fn 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
310/// Navigate to a path, returning an immutable reference.
311fn 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
334/// Parse an array index from a path segment.
335fn parse_array_index(segment: &str) -> Result<usize, PatchError> {
336    // Special case: "-" means end of array (for add)
337    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
346/// Add operation.
347fn op_add(target: &mut Value, path: &str, value: Value) -> Result<(), PatchError> {
348    let segments = parse_pointer(path)?;
349
350    if segments.is_empty() {
351        // Replace the entire document
352        *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                // Append to end
367                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
385/// Remove operation.
386fn 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
419/// Replace operation.
420fn 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
455/// Move operation.
456fn op_move(target: &mut Value, from: &str, to: &str) -> Result<(), PatchError> {
457    // Get the value at 'from'
458    let segments = parse_pointer(from)?;
459    let value = navigate_to(target, &segments)?.clone();
460
461    // Remove from source
462    op_remove(target, from)?;
463
464    // Add to destination
465    op_add(target, to, value)
466}
467
468/// Copy operation.
469fn op_copy(target: &mut Value, from: &str, to: &str) -> Result<(), PatchError> {
470    // Get the value at 'from'
471    let segments = parse_pointer(from)?;
472    let value = navigate_to(target, &segments)
473        .map_err(|_| PatchError::SourceNotFound(from.to_string()))?
474        .clone();
475
476    // Add to destination
477    op_add(target, to, value)
478}
479
480/// Test operation.
481fn 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(), // ~1 encodes /
650            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(), // ~0 encodes ~
662            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    // =========================================================================
723    // PatchError Display Tests
724    // =========================================================================
725
726    #[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    // =========================================================================
792    // JsonPatch Default and Iterator Tests
793    // =========================================================================
794
795    #[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        // patch still valid
829        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    // =========================================================================
845    // Error Cases Tests
846    // =========================================================================
847
848    #[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    // =========================================================================
966    // Additional Operation Tests
967    // =========================================================================
968
969    #[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    // =========================================================================
1065    // Serialization Tests
1066    // =========================================================================
1067
1068    #[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        // Verify can round-trip
1100        let parsed: JsonPatch = serde_json::from_str(&json_str).unwrap();
1101        assert_eq!(parsed.len(), 6);
1102    }
1103
1104    // =========================================================================
1105    // PatchError Clone and PartialEq Tests
1106    // =========================================================================
1107
1108    #[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}