use crate::selector;
pub fn navigate_mut<'a>(
root: &'a mut serde_json::Value,
segments: &[selector::Segment],
create: bool,
) -> anyhow::Result<&'a mut serde_json::Value> {
let mut current = root;
for seg in segments {
current = match seg {
selector::Segment::Key(k) => {
if create {
let needs_create = match current.as_object() {
Some(obj) => !obj.contains_key(k.as_str()),
None => false,
};
if needs_create {
current
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("not an object at key '{k}'"))?
.insert(k.clone(), serde_json::Value::Object(serde_json::Map::new()));
}
}
current
.get_mut(k.as_str())
.ok_or_else(|| anyhow::anyhow!("key not found: {k}"))?
}
selector::Segment::Index(i) => current
.get_mut(*i)
.ok_or_else(|| anyhow::anyhow!("index out of bounds: {i}"))?,
_ => anyhow::bail!("wildcard/predicate not supported in write navigation"),
};
}
Ok(current)
}
fn split_last(segments: &[selector::Segment]) -> (&[selector::Segment], &selector::Segment) {
let (parent, last) = segments.split_at(segments.len() - 1);
(parent, &last[0])
}
pub fn set_at_path(
root: &mut serde_json::Value,
segments: &[selector::Segment],
value: serde_json::Value,
) -> anyhow::Result<()> {
if segments.is_empty() {
anyhow::bail!("empty selector");
}
let (parent_path, last) = split_last(segments);
let parent = navigate_mut(root, parent_path, true)?;
match last {
selector::Segment::Key(k) => {
parent
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("parent is not an object"))?
.insert(k.clone(), value);
}
selector::Segment::Index(i) => {
let arr = parent
.as_array_mut()
.ok_or_else(|| anyhow::anyhow!("parent is not an array"))?;
if *i < arr.len() {
arr[*i] = value;
} else {
anyhow::bail!("index {} out of bounds (len {})", i, arr.len());
}
}
_ => anyhow::bail!("cannot set at wildcard/predicate"),
}
Ok(())
}
pub fn delete_at_selector(
root: &mut serde_json::Value,
segments: &[selector::Segment],
) -> anyhow::Result<bool> {
if segments.is_empty() {
return Ok(false);
}
let (parent_path, last) = split_last(segments);
let parent = match navigate_mut(root, parent_path, false) {
Ok(p) => p,
Err(_) => return Ok(false),
};
match last {
selector::Segment::Key(k) => {
if let Some(obj) = parent.as_object_mut() {
Ok(obj.remove(k.as_str()).is_some())
} else {
Ok(false)
}
}
selector::Segment::Index(i) => {
if let Some(arr) = parent.as_array_mut() {
if *i < arr.len() {
arr.remove(*i);
Ok(true)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
_ => anyhow::bail!("cannot delete at wildcard/predicate"),
}
}
pub fn delete_where(
root: &mut serde_json::Value,
segments: &[selector::Segment],
predicate: &str,
) -> anyhow::Result<usize> {
let eq_pos = predicate
.find('=')
.ok_or_else(|| anyhow::anyhow!("predicate must be in key=value format"))?;
let pred_key = predicate[..eq_pos].trim();
if pred_key.is_empty() {
anyhow::bail!("predicate key is empty; expected key=value format");
}
let raw_val = &predicate[eq_pos + 1..];
if raw_val.starts_with('=') {
anyhow::bail!("predicate uses '==' but only '=' is supported; use key=value format");
}
let pred_val = raw_val.trim();
let target = navigate_mut(root, segments, false)?;
let arr = target
.as_array_mut()
.ok_or_else(|| anyhow::anyhow!("selector does not point to an array"))?;
let before_len = arr.len();
arr.retain(|item| {
item.get(pred_key)
.is_none_or(|field| !selector::value_matches_str(field, pred_val))
});
Ok(before_len - arr.len())
}
pub fn move_at_path(
root: &mut serde_json::Value,
from_segments: &[selector::Segment],
to_segments: &[selector::Segment],
) -> anyhow::Result<()> {
let removed = {
if from_segments.is_empty() {
anyhow::bail!("empty from selector");
}
let (parent_path, last) = split_last(from_segments);
let parent = navigate_mut(root, parent_path, false)?;
match last {
selector::Segment::Key(k) => parent
.as_object_mut()
.and_then(|obj| obj.remove(k.as_str()))
.ok_or_else(|| anyhow::anyhow!("source key '{k}' not found"))?,
selector::Segment::Index(i) => {
let arr = parent
.as_array_mut()
.ok_or_else(|| anyhow::anyhow!("source parent is not an array"))?;
if *i < arr.len() {
arr.remove(*i)
} else {
anyhow::bail!("source index {i} out of bounds");
}
}
_ => anyhow::bail!("cannot move from wildcard/predicate"),
}
};
let (parent_path, last) = split_last(to_segments);
let parent = navigate_mut(root, parent_path, true)?;
match last {
selector::Segment::Key(k) => {
parent
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("target parent is not an object"))?
.insert(k.clone(), removed);
}
selector::Segment::Index(i) => {
let arr = parent
.as_array_mut()
.ok_or_else(|| anyhow::anyhow!("target parent is not an array"))?;
if *i <= arr.len() {
arr.insert(*i, removed);
} else {
anyhow::bail!("target index {i} out of bounds");
}
}
_ => anyhow::bail!("cannot move to wildcard/predicate"),
}
Ok(())
}
const MAX_MERGE_DEPTH: usize = 128;
pub fn deep_merge(base: &mut serde_json::Value, other: &serde_json::Value) {
deep_merge_inner(base, other, 0);
}
fn deep_merge_inner(base: &mut serde_json::Value, other: &serde_json::Value, depth: usize) {
if depth >= MAX_MERGE_DEPTH {
*base = other.clone();
return;
}
if let (Some(base_map), Some(other_map)) = (base.as_object_mut(), other.as_object()) {
for (key, value) in other_map {
let entry = base_map
.entry(key.clone())
.or_insert(serde_json::Value::Null);
deep_merge_inner(entry, value, depth + 1);
}
} else {
*base = other.clone();
}
}
pub fn update_matching(
value: &mut serde_json::Value,
segments: &[selector::Segment],
new_val: &serde_json::Value,
) -> usize {
if segments.is_empty() {
*value = new_val.clone();
return 1;
}
let first = &segments[0];
let rest = &segments[1..];
match first {
selector::Segment::Key(k) => {
if let Some(child) = value.get_mut(k.as_str()) {
update_matching(child, rest, new_val)
} else {
0
}
}
selector::Segment::Index(i) => {
if let Some(child) = value.get_mut(*i) {
update_matching(child, rest, new_val)
} else {
0
}
}
selector::Segment::Wildcard => {
let mut count = 0;
if let Some(arr) = value.as_array_mut() {
for item in arr.iter_mut() {
count += update_matching(item, rest, new_val);
}
}
count
}
selector::Segment::Predicate {
key,
value: pred_val,
} => {
let mut count = 0;
if let Some(arr) = value.as_array_mut() {
for item in arr.iter_mut() {
let matches = item
.get(key.as_str())
.is_some_and(|field| selector::value_matches_str(field, pred_val));
if matches {
count += update_matching(item, rest, new_val);
}
}
}
count
}
}
}