Skip to main content

claw_patch/
json_tree.rs

1use claw_core::types::PatchOp;
2use serde_json::Value;
3
4use crate::codec::Codec;
5use crate::PatchError;
6
7/// Codec that diffs JSON values by path and merges non-overlapping object edits.
8pub struct JsonTreeCodec;
9
10impl Codec for JsonTreeCodec {
11    fn id(&self) -> &str {
12        "json/tree"
13    }
14
15    fn diff(&self, old: &[u8], new: &[u8]) -> Result<Vec<PatchOp>, PatchError> {
16        let old_val: Value =
17            serde_json::from_slice(old).map_err(|e| PatchError::InvalidJson(e.to_string()))?;
18        let new_val: Value =
19            serde_json::from_slice(new).map_err(|e| PatchError::InvalidJson(e.to_string()))?;
20
21        let mut ops = Vec::new();
22        diff_values("", &old_val, &new_val, &mut ops)?;
23        Ok(ops)
24    }
25
26    fn apply(&self, base: &[u8], ops: &[PatchOp]) -> Result<Vec<u8>, PatchError> {
27        let mut val: Value =
28            serde_json::from_slice(base).map_err(|e| PatchError::InvalidJson(e.to_string()))?;
29
30        for op in ops {
31            apply_op(&mut val, op)?;
32        }
33
34        serde_json::to_vec_pretty(&val).map_err(|e| PatchError::ApplyFailed(e.to_string()))
35    }
36
37    fn invert(&self, ops: &[PatchOp]) -> Result<Vec<PatchOp>, PatchError> {
38        let mut inverted: Vec<PatchOp> = ops
39            .iter()
40            .map(|op| match op.op_type.as_str() {
41                "insert" => PatchOp {
42                    address: op.address.clone(),
43                    op_type: "delete".to_string(),
44                    old_data: op.new_data.clone(),
45                    new_data: None,
46                    context_hash: None,
47                },
48                "delete" => PatchOp {
49                    address: op.address.clone(),
50                    op_type: "insert".to_string(),
51                    old_data: None,
52                    new_data: op.old_data.clone(),
53                    context_hash: None,
54                },
55                "replace" => PatchOp {
56                    address: op.address.clone(),
57                    op_type: "replace".to_string(),
58                    old_data: op.new_data.clone(),
59                    new_data: op.old_data.clone(),
60                    context_hash: None,
61                },
62                _ => op.clone(),
63            })
64            .collect();
65        inverted.reverse();
66        Ok(inverted)
67    }
68
69    fn commute(
70        &self,
71        left: &[PatchOp],
72        right: &[PatchOp],
73    ) -> Result<(Vec<PatchOp>, Vec<PatchOp>), PatchError> {
74        // JSON tree commutation: independent paths commute, same path = conflict
75        for l in left {
76            for r in right {
77                let rel = path_relationship(&l.address, &r.address);
78                match rel {
79                    PathRelation::Equal | PathRelation::AncestorOf | PathRelation::DescendantOf => {
80                        return Err(PatchError::CommuteFailed);
81                    }
82                    PathRelation::Independent => {}
83                    PathRelation::SiblingArrayElements => {
84                        // Array index adjustment would be needed for full impl
85                        // Array edits at the same path are treated as non-commutable until
86                        // indexed array operations are represented explicitly.
87                        return Err(PatchError::CommuteFailed);
88                    }
89                }
90            }
91        }
92        // Independent paths: they commute as-is
93        Ok((right.to_vec(), left.to_vec()))
94    }
95
96    fn merge3(&self, base: &[u8], left: &[u8], right: &[u8]) -> Result<Vec<u8>, PatchError> {
97        let base_val: Value =
98            serde_json::from_slice(base).map_err(|e| PatchError::InvalidJson(e.to_string()))?;
99        let left_val: Value =
100            serde_json::from_slice(left).map_err(|e| PatchError::InvalidJson(e.to_string()))?;
101        let right_val: Value =
102            serde_json::from_slice(right).map_err(|e| PatchError::InvalidJson(e.to_string()))?;
103
104        let merged = merge3_values(&base_val, &left_val, &right_val)?;
105        serde_json::to_vec_pretty(&merged).map_err(|e| PatchError::Merge3Failed(e.to_string()))
106    }
107}
108
109fn value_bytes(value: &Value) -> Result<Vec<u8>, PatchError> {
110    serde_json::to_vec(value).map_err(|e| PatchError::InvalidJson(e.to_string()))
111}
112
113fn diff_values(
114    path: &str,
115    old: &Value,
116    new: &Value,
117    ops: &mut Vec<PatchOp>,
118) -> Result<(), PatchError> {
119    if old == new {
120        return Ok(());
121    }
122
123    match (old, new) {
124        (Value::Object(old_map), Value::Object(new_map)) => {
125            // Deleted keys
126            for key in old_map.keys() {
127                if !new_map.contains_key(key) {
128                    let child_path = format!("{path}/{key}");
129                    ops.push(PatchOp {
130                        address: child_path,
131                        op_type: "delete".to_string(),
132                        old_data: Some(value_bytes(&old_map[key])?),
133                        new_data: None,
134                        context_hash: None,
135                    });
136                }
137            }
138            // Added keys
139            for key in new_map.keys() {
140                if !old_map.contains_key(key) {
141                    let child_path = format!("{path}/{key}");
142                    ops.push(PatchOp {
143                        address: child_path,
144                        op_type: "insert".to_string(),
145                        old_data: None,
146                        new_data: Some(value_bytes(&new_map[key])?),
147                        context_hash: None,
148                    });
149                }
150            }
151            // Changed keys
152            for key in old_map.keys() {
153                if let Some(new_val) = new_map.get(key) {
154                    let child_path = format!("{path}/{key}");
155                    diff_values(&child_path, &old_map[key], new_val, ops)?;
156                }
157            }
158        }
159        (Value::Array(old_arr), Value::Array(new_arr)) => {
160            // Simple array diff: if lengths differ or elements differ, replace the whole array
161            if old_arr.len() != new_arr.len() {
162                ops.push(PatchOp {
163                    address: path.to_string(),
164                    op_type: "replace".to_string(),
165                    old_data: Some(value_bytes(old)?),
166                    new_data: Some(value_bytes(new)?),
167                    context_hash: None,
168                });
169            } else {
170                for (i, (o, n)) in old_arr.iter().zip(new_arr.iter()).enumerate() {
171                    let child_path = format!("{path}/{i}");
172                    diff_values(&child_path, o, n, ops)?;
173                }
174            }
175        }
176        _ => {
177            // Scalar or type change: replace
178            ops.push(PatchOp {
179                address: path.to_string(),
180                op_type: "replace".to_string(),
181                old_data: Some(value_bytes(old)?),
182                new_data: Some(value_bytes(new)?),
183                context_hash: None,
184            });
185        }
186    }
187    Ok(())
188}
189
190fn apply_op(val: &mut Value, op: &PatchOp) -> Result<(), PatchError> {
191    let parts: Vec<&str> = op.address.split('/').filter(|s| !s.is_empty()).collect();
192
193    if parts.is_empty() {
194        // Root replacement
195        match op.op_type.as_str() {
196            "replace" => {
197                let new_val: Value =
198                    serde_json::from_slice(op.new_data.as_ref().ok_or_else(|| {
199                        PatchError::ApplyFailed("replace missing new_data".into())
200                    })?)
201                    .map_err(|e| PatchError::ApplyFailed(e.to_string()))?;
202                *val = new_val;
203            }
204            _ => {
205                return Err(PatchError::ApplyFailed(format!(
206                    "unsupported root op: {}",
207                    op.op_type
208                )))
209            }
210        }
211        return Ok(());
212    }
213
214    // Navigate to parent
215    let parent_parts = &parts[..parts.len() - 1];
216    let last_key = parts[parts.len() - 1];
217
218    let mut current = val as &mut Value;
219    for &part in parent_parts {
220        current = navigate_mut(current, part)?;
221    }
222
223    match op.op_type.as_str() {
224        "insert" => {
225            let new_val: Value = serde_json::from_slice(
226                op.new_data
227                    .as_ref()
228                    .ok_or_else(|| PatchError::ApplyFailed("insert missing new_data".into()))?,
229            )
230            .map_err(|e| PatchError::ApplyFailed(e.to_string()))?;
231
232            match current {
233                Value::Object(map) => {
234                    map.insert(last_key.to_string(), new_val);
235                }
236                Value::Array(arr) => {
237                    let idx: usize = last_key
238                        .parse()
239                        .map_err(|_| PatchError::ApplyFailed("invalid array index".into()))?;
240                    if idx > arr.len() {
241                        return Err(PatchError::ApplyFailed("array index out of bounds".into()));
242                    }
243                    arr.insert(idx, new_val);
244                }
245                _ => {
246                    return Err(PatchError::ApplyFailed(
247                        "cannot insert into non-container".into(),
248                    ))
249                }
250            }
251        }
252        "delete" => match current {
253            Value::Object(map) => {
254                map.remove(last_key);
255            }
256            Value::Array(arr) => {
257                let idx: usize = last_key
258                    .parse()
259                    .map_err(|_| PatchError::ApplyFailed("invalid array index".into()))?;
260                if idx >= arr.len() {
261                    return Err(PatchError::ApplyFailed("array index out of bounds".into()));
262                }
263                arr.remove(idx);
264            }
265            _ => {
266                return Err(PatchError::ApplyFailed(
267                    "cannot delete from non-container".into(),
268                ))
269            }
270        },
271        "replace" => {
272            let new_val: Value = serde_json::from_slice(
273                op.new_data
274                    .as_ref()
275                    .ok_or_else(|| PatchError::ApplyFailed("replace missing new_data".into()))?,
276            )
277            .map_err(|e| PatchError::ApplyFailed(e.to_string()))?;
278
279            match current {
280                Value::Object(map) => {
281                    map.insert(last_key.to_string(), new_val);
282                }
283                Value::Array(arr) => {
284                    let idx: usize = last_key
285                        .parse()
286                        .map_err(|_| PatchError::ApplyFailed("invalid array index".into()))?;
287                    if idx >= arr.len() {
288                        return Err(PatchError::ApplyFailed("array index out of bounds".into()));
289                    }
290                    arr[idx] = new_val;
291                }
292                _ => {
293                    return Err(PatchError::ApplyFailed(
294                        "cannot replace in non-container".into(),
295                    ))
296                }
297            }
298        }
299        other => return Err(PatchError::ApplyFailed(format!("unknown op type: {other}"))),
300    }
301
302    Ok(())
303}
304
305fn navigate_mut<'a>(val: &'a mut Value, key: &str) -> Result<&'a mut Value, PatchError> {
306    match val {
307        Value::Object(map) => map
308            .get_mut(key)
309            .ok_or_else(|| PatchError::ApplyFailed(format!("key not found: {key}"))),
310        Value::Array(arr) => {
311            let idx: usize = key
312                .parse()
313                .map_err(|_| PatchError::ApplyFailed(format!("invalid index: {key}")))?;
314            arr.get_mut(idx)
315                .ok_or_else(|| PatchError::ApplyFailed(format!("index out of bounds: {idx}")))
316        }
317        _ => Err(PatchError::ApplyFailed(format!(
318            "cannot navigate into scalar at {key}"
319        ))),
320    }
321}
322
323#[derive(Debug, PartialEq)]
324enum PathRelation {
325    Equal,
326    AncestorOf,
327    DescendantOf,
328    SiblingArrayElements,
329    Independent,
330}
331
332fn path_relationship(a: &str, b: &str) -> PathRelation {
333    if a == b {
334        return PathRelation::Equal;
335    }
336    if b.starts_with(a) && b.as_bytes().get(a.len()) == Some(&b'/') {
337        return PathRelation::AncestorOf;
338    }
339    if a.starts_with(b) && a.as_bytes().get(b.len()) == Some(&b'/') {
340        return PathRelation::DescendantOf;
341    }
342    // Check if siblings in same array
343    let a_parts: Vec<&str> = a.split('/').collect();
344    let b_parts: Vec<&str> = b.split('/').collect();
345    if a_parts.len() == b_parts.len() && a_parts.len() > 1 {
346        let a_parent = &a_parts[..a_parts.len() - 1];
347        let b_parent = &b_parts[..b_parts.len() - 1];
348        if a_parent == b_parent {
349            let Some(a_last) = a_parts.last() else {
350                return PathRelation::Independent;
351            };
352            let Some(b_last) = b_parts.last() else {
353                return PathRelation::Independent;
354            };
355            if a_last.parse::<usize>().is_ok() && b_last.parse::<usize>().is_ok() {
356                return PathRelation::SiblingArrayElements;
357            }
358        }
359    }
360    PathRelation::Independent
361}
362
363fn merge3_values(base: &Value, left: &Value, right: &Value) -> Result<Value, PatchError> {
364    if left == right {
365        return Ok(left.clone());
366    }
367    if left == base {
368        return Ok(right.clone());
369    }
370    if right == base {
371        return Ok(left.clone());
372    }
373
374    match (base, left, right) {
375        (Value::Object(base_map), Value::Object(left_map), Value::Object(right_map)) => {
376            let mut merged = serde_json::Map::new();
377            let all_keys: std::collections::BTreeSet<&String> = base_map
378                .keys()
379                .chain(left_map.keys())
380                .chain(right_map.keys())
381                .collect();
382
383            for key in all_keys {
384                let b = base_map.get(key);
385                let l = left_map.get(key);
386                let r = right_map.get(key);
387
388                match (b, l, r) {
389                    (Some(bv), Some(lv), Some(rv)) => {
390                        merged.insert(key.clone(), merge3_values(bv, lv, rv)?);
391                    }
392                    (Some(_), Some(_lv), None) => {
393                        // Right deleted, left kept or modified
394                        if l == b {
395                            // Left didn't change, right deleted - accept deletion
396                        } else {
397                            // Left modified, right deleted - conflict
398                            return Err(PatchError::Merge3Failed(format!(
399                                "conflict at /{key}: left modified, right deleted"
400                            )));
401                        }
402                    }
403                    (Some(_), None, Some(_rv)) => {
404                        if r == b {
405                            // Right didn't change, left deleted - accept deletion
406                        } else {
407                            return Err(PatchError::Merge3Failed(format!(
408                                "conflict at /{key}: left deleted, right modified"
409                            )));
410                        }
411                    }
412                    (None, Some(lv), Some(rv)) => {
413                        // Both added
414                        if lv == rv {
415                            merged.insert(key.clone(), lv.clone());
416                        } else {
417                            return Err(PatchError::Merge3Failed(format!(
418                                "conflict at /{key}: both sides added different values"
419                            )));
420                        }
421                    }
422                    (None, Some(lv), None) => {
423                        merged.insert(key.clone(), lv.clone());
424                    }
425                    (None, None, Some(rv)) => {
426                        merged.insert(key.clone(), rv.clone());
427                    }
428                    (Some(_), None, None) => {
429                        // Both deleted - ok
430                    }
431                    (None, None, None) => unreachable!(),
432                }
433            }
434            Ok(Value::Object(merged))
435        }
436        _ => {
437            // Both changed differently - conflict
438            Err(PatchError::Merge3Failed(
439                "both sides changed scalar/array value differently".to_string(),
440            ))
441        }
442    }
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use serde_json::json;
449
450    #[test]
451    fn diff_and_apply_json() {
452        let codec = JsonTreeCodec;
453        let old = serde_json::to_vec(&json!({"a": 1, "b": 2})).unwrap();
454        let new = serde_json::to_vec(&json!({"a": 1, "b": 3, "c": 4})).unwrap();
455        let ops = codec.diff(&old, &new).unwrap();
456        let result = codec.apply(&old, &ops).unwrap();
457        let result_val: Value = serde_json::from_slice(&result).unwrap();
458        assert_eq!(result_val, json!({"a": 1, "b": 3, "c": 4}));
459    }
460
461    #[test]
462    fn json_merge3_no_conflict() {
463        let codec = JsonTreeCodec;
464        let base = serde_json::to_vec(&json!({"a": 1, "b": 2, "c": 3})).unwrap();
465        let left = serde_json::to_vec(&json!({"a": 10, "b": 2, "c": 3})).unwrap();
466        let right = serde_json::to_vec(&json!({"a": 1, "b": 20, "c": 3})).unwrap();
467        let merged = codec.merge3(&base, &left, &right).unwrap();
468        let merged_val: Value = serde_json::from_slice(&merged).unwrap();
469        assert_eq!(merged_val, json!({"a": 10, "b": 20, "c": 3}));
470    }
471
472    #[test]
473    fn json_merge3_both_add_same_key_different_value() {
474        let codec = JsonTreeCodec;
475        let base = serde_json::to_vec(&json!({"a": 1})).unwrap();
476        let left = serde_json::to_vec(&json!({"a": 1, "new": "left"})).unwrap();
477        let right = serde_json::to_vec(&json!({"a": 1, "new": "right"})).unwrap();
478        assert!(codec.merge3(&base, &left, &right).is_err());
479    }
480
481    #[test]
482    fn json_invert_roundtrip() {
483        let codec = JsonTreeCodec;
484        let old = serde_json::to_vec(&json!({"x": 1, "y": 2})).unwrap();
485        let new = serde_json::to_vec(&json!({"x": 10, "y": 2, "z": 3})).unwrap();
486        let ops = codec.diff(&old, &new).unwrap();
487        let applied = codec.apply(&old, &ops).unwrap();
488        let applied_val: Value = serde_json::from_slice(&applied).unwrap();
489        assert_eq!(applied_val, json!({"x": 10, "y": 2, "z": 3}));
490
491        let inv = codec.invert(&ops).unwrap();
492        let restored = codec.apply(&applied, &inv).unwrap();
493        let restored_val: Value = serde_json::from_slice(&restored).unwrap();
494        assert_eq!(restored_val, json!({"x": 1, "y": 2}));
495    }
496
497    #[test]
498    fn path_relation_tests() {
499        assert_eq!(path_relationship("/a/b", "/a/b"), PathRelation::Equal);
500        assert_eq!(path_relationship("/a", "/a/b"), PathRelation::AncestorOf);
501        assert_eq!(path_relationship("/a/b", "/a"), PathRelation::DescendantOf);
502        assert_eq!(
503            path_relationship("/a/0", "/a/1"),
504            PathRelation::SiblingArrayElements
505        );
506        assert_eq!(path_relationship("/a", "/b"), PathRelation::Independent);
507    }
508}