use serde_json::Value;
use super::Signature;
const EVERY: &str = "[]";
fn segments(path: &str) -> Vec<&str> {
path.split('/').skip(1).collect()
}
pub(crate) fn spelled(entry: &str) -> (&str, &str) {
entry.rsplit_once(':').unwrap_or((entry, ""))
}
pub(crate) fn words(signature: &Signature) -> Vec<String> {
signature
.keys()
.map(|entry| spelled(entry))
.filter(|(path, kind)| *kind == "string" && *path != "/kind" && !path.is_empty())
.map(|(path, _)| path.to_owned())
.collect()
}
pub(crate) fn project(frame: &Value, signature: &Signature, at: u32) -> Value {
let mut projected = frame.clone();
for (entry, stamp) in signature {
if u32::try_from(*stamp).is_ok_and(|stamp| stamp <= at) {
continue;
}
let (path, _) = spelled(entry);
remove(&mut projected, &segments(path));
}
projected
}
fn remove(value: &mut Value, segments: &[&str]) {
let Some((leaf, above)) = segments.split_last() else {
return;
};
walk(value, above, &mut |held| {
if *leaf == EVERY {
if let Some(array) = held.as_array_mut() {
array.clear();
}
} else if let Some(object) = held.as_object_mut() {
object.remove(*leaf);
}
});
}
pub(crate) fn mutate(frame: &Value, path: &str, word: &str) -> Option<Value> {
let mut mutated = frame.clone();
let segments = segments(path);
let (leaf, above) = segments.split_last()?;
let mut set = false;
walk(&mut mutated, above, &mut |held| {
if *leaf == EVERY {
for element in held.as_array_mut().into_iter().flatten() {
set |= replace(element, word);
}
} else if let Some(at) = held.as_object_mut().and_then(|o| o.get_mut(*leaf)) {
set |= replace(at, word);
}
});
set.then_some(mutated)
}
fn replace(at: &mut Value, word: &str) -> bool {
if !at.is_string() {
return false;
}
*at = Value::String(word.to_owned());
true
}
fn walk(value: &mut Value, segments: &[&str], act: &mut dyn FnMut(&mut Value)) {
let Some((head, rest)) = segments.split_first() else {
act(value);
return;
};
if *head == EVERY {
for element in value.as_array_mut().into_iter().flatten() {
walk(element, rest, act);
}
} else if let Some(held) = value.get_mut(*head) {
walk(held, rest, act);
}
}
#[cfg(test)]
mod tests;