119 lines
4.3 KiB
Rust
119 lines
4.3 KiB
Rust
use serde_json::Value;
|
|
|
|
use crate::tx::types::PatchOp;
|
|
|
|
pub fn json_pointer_parent<'a>(
|
|
root: &'a mut Value,
|
|
path: &str,
|
|
) -> Result<(&'a mut Value, String), String> {
|
|
if !path.starts_with('/') && path != "" {
|
|
return Err("path must be JSON pointer starting with '/'".into());
|
|
}
|
|
|
|
let part: Vec<String> = path
|
|
.split('/')
|
|
.skip(1)
|
|
.map(|x| x.replace("~1", "/").replace("~0", "~"))
|
|
.collect();
|
|
if path.is_empty() {
|
|
return Ok((root, "".into()));
|
|
}
|
|
|
|
let last = part.last().unwrap().clone();
|
|
|
|
let mut cur = root;
|
|
|
|
for key in &part[..part.len() - 1] {
|
|
match cur {
|
|
Value::Object(map) => {
|
|
cur = map
|
|
.get_mut(key)
|
|
.ok_or_else(|| format!("missing object key {key}"))?;
|
|
}
|
|
Value::Array(arr) => {
|
|
let idx: usize = key.parse().map_err(|_| format!("bad array idx {key}"))?;
|
|
cur = arr
|
|
.get_mut(idx)
|
|
.ok_or_else(|| format!("array index out of bounds {idx}"))?;
|
|
}
|
|
_ => return Err("cannot traverse non-container".into()),
|
|
}
|
|
}
|
|
|
|
Ok((cur, last))
|
|
}
|
|
|
|
pub fn apply_ops(mut state: Value, ops: &[PatchOp]) -> Result<Value, String> {
|
|
for op in ops {
|
|
match op {
|
|
PatchOp::Replace { path, value } => {
|
|
if path == "" || path == "/" {
|
|
state = value.clone();
|
|
continue;
|
|
}
|
|
let (parent, last) = json_pointer_parent(&mut state, path)?;
|
|
match parent {
|
|
Value::Object(map) => {
|
|
if !map.contains_key(&last) {
|
|
return Err(format!("replace target missing key {last}"));
|
|
}
|
|
map.insert(last, value.clone());
|
|
}
|
|
Value::Array(arr) => {
|
|
let idx: usize = last
|
|
.parse()
|
|
.map_err(|_| format!("bad array index {last}"))?;
|
|
if idx >= arr.len() {
|
|
return Err(format!("replace index out of bounds {idx}"));
|
|
}
|
|
arr[idx] = value.clone();
|
|
}
|
|
_ => return Err("replace parent not container".into()),
|
|
}
|
|
}
|
|
PatchOp::Add { path, value } => {
|
|
let (parent, last) = json_pointer_parent(&mut state, path)?;
|
|
match parent {
|
|
Value::Object(map) => {
|
|
map.insert(last, value.clone());
|
|
}
|
|
Value::Array(arr) => {
|
|
if last == "-" {
|
|
arr.push(value.clone());
|
|
} else {
|
|
let idx: usize = last
|
|
.parse()
|
|
.map_err(|_| format!("bad array index {last}"))?;
|
|
if idx > arr.len() {
|
|
return Err(format!("add index out of bounds {idx}"));
|
|
}
|
|
arr.insert(idx, value.clone());
|
|
}
|
|
}
|
|
_ => return Err("add parent not container".into()),
|
|
}
|
|
}
|
|
PatchOp::Remove { path } => {
|
|
let (parent, last) = json_pointer_parent(&mut state, path)?;
|
|
match parent {
|
|
Value::Object(map) => {
|
|
map.remove(&last)
|
|
.ok_or_else(|| format!("remove missing key {last}"))?;
|
|
}
|
|
Value::Array(arr) => {
|
|
let idx: usize = last
|
|
.parse()
|
|
.map_err(|_| format!("bad array index {last}"))?;
|
|
if idx > arr.len() {
|
|
return Err(format!("remove index out of bounds {idx}"));
|
|
}
|
|
arr.remove(idx);
|
|
}
|
|
_ => return Err("remove parent not container".into()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(state)
|
|
}
|