use yo_common::{Code, Error, Result};
use crate::build::Builder;
use crate::head::Kind;
use crate::read::Value;
#[derive(Debug, Clone, Copy)]
pub enum Edit<'a> {
Set(&'a [u8]),
Remove,
Put(&'a [u8], &'a [u8]),
Splice {
at: usize,
take: usize,
put: &'a [&'a [u8]],
},
}
pub fn edit(root: &Value<'_>, at: &[(usize, Edit<'_>)]) -> Result<Vec<u8>> {
if at
.iter()
.any(|(off, e)| *off == 0 && matches!(e, Edit::Remove))
{
return Err(no_root());
}
let mut done = vec![false; at.len()];
let mut b = Builder::new();
write(root, root, at, &mut done, &mut b)?;
if done.contains(&false) {
return Err(stray());
}
Ok(b.finish()?.to_vec())
}
fn write(
root: &Value<'_>,
v: &Value<'_>,
at: &[(usize, Edit<'_>)],
done: &mut [bool],
b: &mut Builder,
) -> Result<()> {
let off = v.offset_in(root).ok_or_else(stray)?;
let Some(what) = find(at, off) else {
if !inside(root, v, at)? {
return b.embed(v);
}
return match v.kind() {
Kind::Object => object(root, v, at, done, b),
Kind::Array => array(root, v, at, done, b),
_ => Err(stray()),
};
};
mark(at, done, off, off + v.encoded_len().ok_or_else(unreadable)?);
match what {
Edit::Set(bytes) => {
let new = Value::new(bytes)
.ok_or_else(|| Error::new(Code::Invalid, "the value written is not readable"))?;
b.embed(&new)
}
Edit::Remove => Err(no_root()),
Edit::Put(key, value) => put(root, v, key, value, at, done, b),
Edit::Splice {
at: from,
take,
put,
} => splice(root, v, Run { from, take, put }, at, done, b),
}
}
fn object(
root: &Value<'_>,
v: &Value<'_>,
at: &[(usize, Edit<'_>)],
done: &mut [bool],
b: &mut Builder,
) -> Result<()> {
let interned = v.is_interned();
if interned {
b.begin_object_interned()?;
} else {
b.begin_object()?;
}
for i in 0..v.len() {
let child = v.at(i).ok_or_else(unreadable)?;
if skipped(root, &child, at, done)? {
continue;
}
if interned {
b.key_id(v.key_id_at(i).ok_or_else(unreadable)?)?;
} else {
b.key(v.key_at(i).ok_or_else(unreadable)?)?;
}
write(root, &child, at, done, b)?;
}
b.end_object()
}
fn array(
root: &Value<'_>,
v: &Value<'_>,
at: &[(usize, Edit<'_>)],
done: &mut [bool],
b: &mut Builder,
) -> Result<()> {
b.begin_array()?;
for i in 0..v.len() {
let child = v.at(i).ok_or_else(unreadable)?;
if skipped(root, &child, at, done)? {
continue;
}
write(root, &child, at, done, b)?;
}
b.end_array()
}
fn put(
root: &Value<'_>,
v: &Value<'_>,
key: &[u8],
value: &[u8],
at: &[(usize, Edit<'_>)],
done: &mut [bool],
b: &mut Builder,
) -> Result<()> {
if v.kind() != Kind::Object {
return Err(Error::new(
Code::Invalid,
"a key can only be put into an object",
));
}
if v.is_interned() {
return Err(Error::new(
Code::Invalid,
"an object whose keys are interned needs the collection's key table to take a new key",
));
}
let new = Value::new(value)
.ok_or_else(|| Error::new(Code::Invalid, "the value put is not readable"))?;
b.begin_object()?;
for i in 0..v.len() {
let child = v.at(i).ok_or_else(unreadable)?;
if skipped(root, &child, at, done)? {
continue;
}
b.key(v.key_at(i).ok_or_else(unreadable)?)?;
write(root, &child, at, done, b)?;
}
b.key(key)?;
b.embed(&new)?;
b.end_object()
}
struct Run<'a> {
from: usize,
take: usize,
put: &'a [&'a [u8]],
}
fn splice(
root: &Value<'_>,
v: &Value<'_>,
run: Run<'_>,
at: &[(usize, Edit<'_>)],
done: &mut [bool],
b: &mut Builder,
) -> Result<()> {
if v.kind() != Kind::Array {
return Err(Error::new(
Code::Invalid,
"elements can only be spliced into an array",
));
}
let n = v.len();
let from = run.from.min(n);
let end = from.saturating_add(run.take).min(n);
b.begin_array()?;
let kept = |i: usize, b: &mut Builder, done: &mut [bool]| -> Result<()> {
let child = v.at(i).ok_or_else(unreadable)?;
if skipped(root, &child, at, done)? {
return Ok(());
}
write(root, &child, at, done, b)
};
for i in 0..from {
kept(i, b, done)?;
}
for bytes in run.put {
let new = Value::new(bytes)
.ok_or_else(|| Error::new(Code::Invalid, "an element written is not readable"))?;
b.embed(&new)?;
}
for i in end..n {
kept(i, b, done)?;
}
b.end_array()
}
fn find<'e>(at: &[(usize, Edit<'e>)], off: usize) -> Option<Edit<'e>> {
at.iter().find(|(o, _)| *o == off).map(|(_, e)| *e)
}
fn mark(at: &[(usize, Edit<'_>)], done: &mut [bool], off: usize, end: usize) {
for (i, (o, _)) in at.iter().enumerate() {
if *o == off || (*o > off && *o < end) {
done[i] = true;
}
}
}
fn inside(root: &Value<'_>, v: &Value<'_>, at: &[(usize, Edit<'_>)]) -> Result<bool> {
let off = v.offset_in(root).ok_or_else(stray)?;
let end = off + v.encoded_len().ok_or_else(unreadable)?;
Ok(at.iter().any(|(o, _)| *o > off && *o < end))
}
fn skipped(
root: &Value<'_>,
child: &Value<'_>,
at: &[(usize, Edit<'_>)],
done: &mut [bool],
) -> Result<bool> {
let off = child.offset_in(root).ok_or_else(stray)?;
if !matches!(find(at, off), Some(Edit::Remove)) {
return Ok(false);
}
mark(
at,
done,
off,
off + child.encoded_len().ok_or_else(unreadable)?,
);
Ok(true)
}
fn no_root() -> Error {
Error::new(
Code::Invalid,
"the whole document cannot be removed, only the key it is under",
)
}
fn stray() -> Error {
Error::new(
Code::Invalid,
"an offset that no value in this document begins at",
)
}
fn unreadable() -> Error {
Error::new(Code::Corrupt, "the document being edited is not readable")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Path;
use crate::text::from_json;
fn hits(doc: &[u8], path: &[u8]) -> Vec<usize> {
let root = Value::new(doc).expect("readable");
let mut found = Vec::new();
Path::parse(path)
.expect("the path parses")
.select(&root, &mut found);
found
.iter()
.map(|v| v.offset_in(&root).expect("from this document"))
.collect()
}
fn changed(text: &str, path: &[u8], what: Edit<'_>) -> String {
let doc = from_json(text.as_bytes()).expect("the text parses");
let root = Value::new(&doc).expect("readable");
let at: Vec<_> = hits(&doc, path)
.into_iter()
.map(|off| (off, what))
.collect();
let after = edit(&root, &at).expect("the edit applies");
let out = Value::new(&after).expect("readable");
assert!(out.validate(), "the document that came out is whole");
String::from_utf8(out.to_json().expect("writable")).expect("UTF-8")
}
#[test]
fn a_value_is_replaced_wherever_the_path_names_it() {
let nine = from_json(b"9").expect("parses");
assert_eq!(
changed(r#"{"a":{"n":1},"b":{"n":2}}"#, b"$..n", Edit::Set(&nine)),
r#"{"a":{"n":9},"b":{"n":9}}"#
);
assert_eq!(
changed(r#"{"a":[1,2,3]}"#, b"$.a[*]", Edit::Set(&nine)),
r#"{"a":[9,9,9]}"#
);
assert_eq!(changed("[1,2]", b"$", Edit::Set(&nine)), "9");
}
#[test]
fn a_value_that_is_removed_leaves_no_hole() {
assert_eq!(
changed(r#"{"a":1,"bb":2,"cc":3}"#, b"$.bb", Edit::Remove),
r#"{"a":1,"cc":3}"#
);
assert_eq!(changed("[1,2,3]", b"$[1]", Edit::Remove), "[1,3]");
assert_eq!(changed("[1,2,3]", b"$[*]", Edit::Remove), "[]");
assert_eq!(
changed(r#"{"a":{"n":1},"b":{"n":2}}"#, b"$..n", Edit::Remove),
r#"{"a":{},"b":{}}"#
);
}
#[test]
fn a_removal_inside_a_removal_is_dropped_rather_than_refused() {
assert_eq!(
changed(r#"{"a":{"a":1},"b":2}"#, b"$..a", Edit::Remove),
r#"{"b":2}"#
);
}
#[test]
fn the_whole_document_cannot_be_removed() {
let doc = from_json(br#"{"a":1}"#).expect("parses");
let root = Value::new(&doc).expect("readable");
let why = edit(&root, &[(0, Edit::Remove)]).unwrap_err();
assert!(
why.message().contains("only the key it is under"),
"{}",
why.message()
);
}
#[test]
fn a_key_put_into_an_object_lands_in_key_order() {
let one = from_json(b"1").expect("parses");
assert_eq!(
changed(r#"{"aa":1,"cc":3}"#, b"$", Edit::Put(b"bb", &one)),
r#"{"aa":1,"bb":1,"cc":3}"#
);
assert_eq!(
changed(r#"{"aa":1,"cc":3}"#, b"$", Edit::Put(b"cc", &one)),
r#"{"aa":1,"cc":1}"#
);
assert_eq!(
changed(
r#"{"a":{"x":1},"b":{"x":2}}"#,
b"$.*",
Edit::Put(b"n", &one)
),
r#"{"a":{"n":1,"x":1},"b":{"n":1,"x":2}}"#
);
}
#[test]
fn a_key_cannot_be_put_into_something_that_is_not_an_object() {
let one = from_json(b"1").expect("parses");
let doc = from_json(b"[1,2]").expect("parses");
let root = Value::new(&doc).expect("readable");
let why = edit(&root, &[(0, Edit::Put(b"a", &one))]).unwrap_err();
assert!(
why.message().contains("only be put into an object"),
"{}",
why.message()
);
}
#[test]
fn a_splice_covers_appending_inserting_popping_and_trimming() {
let seven = from_json(b"7").expect("parses");
let eight = from_json(b"8").expect("parses");
let put: &[&[u8]] = &[&seven, &eight];
let append = Edit::Splice {
at: usize::MAX,
take: 0,
put,
};
assert_eq!(changed("[1,2]", b"$", append), "[1,2,7,8]");
let insert = Edit::Splice {
at: 1,
take: 0,
put,
};
assert_eq!(changed("[1,2]", b"$", insert), "[1,7,8,2]");
let pop = Edit::Splice {
at: 2,
take: 1,
put: &[],
};
assert_eq!(changed("[1,2,3]", b"$", pop), "[1,2]");
let kept: &[&[u8]] = &[&seven];
let trim = Edit::Splice {
at: 0,
take: usize::MAX,
put: kept,
};
assert_eq!(changed("[1,2,3]", b"$", trim), "[7]");
let each = Edit::Splice {
at: 0,
take: 0,
put: kept,
};
assert_eq!(
changed(r#"{"a":[1],"b":[2]}"#, b"$..a", each),
r#"{"a":[7,1],"b":[2]}"#
);
}
#[test]
fn elements_cannot_be_spliced_into_something_that_is_not_an_array() {
let doc = from_json(br#"{"a":1}"#).expect("parses");
let root = Value::new(&doc).expect("readable");
let what = Edit::Splice {
at: 0,
take: 0,
put: &[],
};
let why = edit(&root, &[(0, what)]).unwrap_err();
assert!(
why.message().contains("only be spliced into an array"),
"{}",
why.message()
);
}
#[test]
fn everything_the_edit_did_not_name_comes_back_byte_identical() {
let text = br#"{"keep":[{"deep":[1,2,{"here":true}]},"text",null],"n":1.5,"go":0}"#;
let doc = from_json(text).expect("parses");
let root = Value::new(&doc).expect("readable");
let at: Vec<_> = hits(&doc, b"$.go")
.into_iter()
.map(|off| (off, Edit::Remove))
.collect();
let after = edit(&root, &at).expect("applies");
let out = Value::new(&after).expect("readable");
assert_eq!(
out.get(b"keep").expect("still there").as_bytes(),
root.get(b"keep").expect("was there").as_bytes(),
"the part nobody touched is the same bytes"
);
assert!(out.get(b"go").is_none());
assert_eq!(out.len(), 2);
}
#[test]
fn a_splice_or_a_put_still_carries_the_edits_inside_what_it_keeps() {
let one = from_json(b"1").expect("parses");
let put: &[&[u8]] = &[&one];
assert_eq!(
changed(
r#"{"a":[{"a":[7]}]}"#,
b"$..a",
Edit::Splice {
at: usize::MAX,
take: 0,
put,
}
),
r#"{"a":[{"a":[7,1]},1]}"#
);
assert_eq!(
changed(
r#"{"o":{"o":{}}}"#,
b"$..o",
Edit::Put(b"n", one.as_slice())
),
r#"{"o":{"n":1,"o":{"n":1}}}"#
);
assert_eq!(
changed(
r#"{"a":[{"a":[7]}]}"#,
b"$..a",
Edit::Splice {
at: 0,
take: usize::MAX,
put: &[],
}
),
r#"{"a":[]}"#
);
}
#[test]
fn no_edits_at_all_is_the_document_it_was_given() {
let doc = from_json(br#"{"a":[1,{"b":"c"}],"d":null}"#).expect("parses");
let root = Value::new(&doc).expect("readable");
assert_eq!(edit(&root, &[]).expect("applies"), doc);
}
#[test]
fn an_offset_that_is_not_a_value_says_so() {
let doc = from_json(br#"{"a":1}"#).expect("parses");
let root = Value::new(&doc).expect("readable");
let one = from_json(b"1").expect("parses");
let why = edit(&root, &[(2, Edit::Set(&one))]).unwrap_err();
assert!(
why.message()
.contains("no value in this document begins at"),
"{}",
why.message()
);
let why = edit(&root, &[(doc.len() + 8, Edit::Set(&one))]).unwrap_err();
assert!(
why.message()
.contains("no value in this document begins at"),
"{}",
why.message()
);
}
}