mockforge_core/overrides/
patcher.rs

1//! JSON patch application logic
2//!
3//! This module handles applying JSON patches to values with support
4//! for both replace and merge modes.
5
6use json_patch::{AddOperation, PatchOperation, RemoveOperation, ReplaceOperation};
7use jsonptr::PointerBuf;
8use serde_json::Value;
9
10use super::models::PatchOp;
11use crate::templating::expand_tokens;
12
13/// Apply a patch operation to a JSON document
14pub fn apply_patch(doc: &mut Value, op: &PatchOp) -> anyhow::Result<()> {
15    let ops = match op {
16        PatchOp::Add { path, value } => vec![PatchOperation::Add(AddOperation {
17            path: path.parse().unwrap_or_else(|_| PointerBuf::new()),
18            value: value.clone(),
19        })],
20        PatchOp::Replace { path, value } => vec![PatchOperation::Replace(ReplaceOperation {
21            path: path.parse().unwrap_or_else(|_| PointerBuf::new()),
22            value: value.clone(),
23        })],
24        PatchOp::Remove { path } => vec![PatchOperation::Remove(RemoveOperation {
25            path: path.parse().unwrap_or_else(|_| PointerBuf::new()),
26        })],
27    };
28
29    // Apply the patch using the correct function
30    json_patch::patch(doc, &ops)?;
31    Ok(())
32}
33
34/// Apply a merge-style patch operation
35pub fn apply_merge_patch(doc: &mut Value, op: &PatchOp) -> anyhow::Result<()> {
36    match op {
37        PatchOp::Add { path, value } | PatchOp::Replace { path, value } => {
38            // For merge mode, we deep merge objects instead of replacing
39            merge_at_path(doc, path, value)?;
40        }
41        PatchOp::Remove { path: _ } => {
42            // Remove operations work the same in both modes
43            apply_patch(doc, op)?;
44        }
45    }
46    Ok(())
47}
48
49/// Deep merge a value at a JSON path
50fn merge_at_path(doc: &mut Value, path: &str, value: &Value) -> anyhow::Result<()> {
51    if path.is_empty() || path == "/" {
52        // Root merge
53        merge_values(doc, value);
54        return Ok(());
55    }
56
57    let ptr: PointerBuf = path.parse().unwrap_or_else(|_| PointerBuf::new());
58    let mut current = doc;
59
60    // Navigate to the parent of the target location
61    let segments = ptr.as_str().trim_start_matches('/').split('/').collect::<Vec<_>>();
62    for (i, segment) in segments.iter().enumerate() {
63        let decoded_segment = segment.replace("~1", "/").replace("~0", "~");
64
65        if i == segments.len() - 1 {
66            // Last segment - this is where we merge
67            match current {
68                Value::Object(map) => {
69                    if let Some(existing) = map.get_mut(&decoded_segment) {
70                        merge_values(existing, value);
71                    } else {
72                        map.insert(decoded_segment, value.clone());
73                    }
74                }
75                _ => {
76                    // Create an object if it doesn't exist
77                    *current = serde_json::json!({ decoded_segment: value });
78                }
79            }
80        } else {
81            // Navigate deeper
82            match current {
83                Value::Object(map) => {
84                    current =
85                        map.entry(decoded_segment).or_insert(Value::Object(serde_json::Map::new()));
86                }
87                _ => {
88                    // Create nested structure
89                    let mut new_obj = serde_json::Map::new();
90                    new_obj
91                        .insert(decoded_segment.to_string(), Value::Object(serde_json::Map::new()));
92                    *current = Value::Object(new_obj);
93                    current = &mut current[decoded_segment];
94                }
95            }
96        }
97    }
98
99    Ok(())
100}
101
102/// Deep merge two JSON values
103fn merge_values(target: &mut Value, source: &Value) {
104    match target {
105        Value::Object(target_map) => {
106            if let Value::Object(source_map) = source {
107                for (key, source_value) in source_map {
108                    if let Some(target_value) = target_map.get_mut(key) {
109                        merge_values(target_value, source_value);
110                    } else {
111                        target_map.insert(key.clone(), source_value.clone());
112                    }
113                }
114            } else {
115                // Replace with source if types don't match
116                *target = source.clone();
117            }
118        }
119        Value::Array(target_arr) => {
120            if let Value::Array(source_arr) = source {
121                target_arr.extend(source_arr.clone());
122            } else {
123                // Replace with source if types don't match
124                *target = source.clone();
125            }
126        }
127        _ => {
128            // For primitives or other types, replace
129            *target = source.clone();
130        }
131    }
132}
133
134/// Apply post-templating expansion to a value
135pub fn apply_post_templating(value: &mut Value) {
136    *value = expand_tokens(value);
137}