use json_patch::{AddOperation, PatchOperation, RemoveOperation, ReplaceOperation};
use jsonptr::PointerBuf;
use serde_json::Value;
use super::models::PatchOp;
use crate::templating::expand_tokens;
pub fn apply_patch(doc: &mut Value, op: &PatchOp) -> anyhow::Result<()> {
let ops = match op {
PatchOp::Add { path, value } => vec![PatchOperation::Add(AddOperation {
path: path.parse().unwrap_or_else(|_| PointerBuf::new()),
value: value.clone(),
})],
PatchOp::Replace { path, value } => vec![PatchOperation::Replace(ReplaceOperation {
path: path.parse().unwrap_or_else(|_| PointerBuf::new()),
value: value.clone(),
})],
PatchOp::Remove { path } => vec![PatchOperation::Remove(RemoveOperation {
path: path.parse().unwrap_or_else(|_| PointerBuf::new()),
})],
};
json_patch::patch(doc, &ops)?;
Ok(())
}
pub fn apply_merge_patch(doc: &mut Value, op: &PatchOp) -> anyhow::Result<()> {
match op {
PatchOp::Add { path, value } | PatchOp::Replace { path, value } => {
merge_at_path(doc, path, value)?;
}
PatchOp::Remove { path: _ } => {
apply_patch(doc, op)?;
}
}
Ok(())
}
fn merge_at_path(doc: &mut Value, path: &str, value: &Value) -> anyhow::Result<()> {
if path.is_empty() || path == "/" {
merge_values(doc, value);
return Ok(());
}
let ptr: PointerBuf = path.parse().unwrap_or_else(|_| PointerBuf::new());
let mut current = doc;
let segments = ptr.as_str().trim_start_matches('/').split('/').collect::<Vec<_>>();
for (i, segment) in segments.iter().enumerate() {
let decoded_segment = segment.replace("~1", "/").replace("~0", "~");
if i == segments.len() - 1 {
match current {
Value::Object(map) => {
if let Some(existing) = map.get_mut(&decoded_segment) {
merge_values(existing, value);
} else {
map.insert(decoded_segment, value.clone());
}
}
_ => {
*current = serde_json::json!({ decoded_segment: value });
}
}
} else {
match current {
Value::Object(map) => {
current =
map.entry(decoded_segment).or_insert(Value::Object(serde_json::Map::new()));
}
_ => {
let mut new_obj = serde_json::Map::new();
new_obj
.insert(decoded_segment.to_string(), Value::Object(serde_json::Map::new()));
*current = Value::Object(new_obj);
current = &mut current[decoded_segment];
}
}
}
}
Ok(())
}
fn merge_values(target: &mut Value, source: &Value) {
match target {
Value::Object(target_map) => {
if let Value::Object(source_map) = source {
for (key, source_value) in source_map {
if let Some(target_value) = target_map.get_mut(key) {
merge_values(target_value, source_value);
} else {
target_map.insert(key.clone(), source_value.clone());
}
}
} else {
*target = source.clone();
}
}
Value::Array(target_arr) => {
if let Value::Array(source_arr) = source {
target_arr.extend(source_arr.clone());
} else {
*target = source.clone();
}
}
_ => {
*target = source.clone();
}
}
}
pub fn apply_post_templating(value: &mut Value) {
*value = expand_tokens(value);
}