#![allow(
clippy::indexing_slicing,
clippy::panic,
clippy::string_slice,
clippy::unwrap_used,
reason = "test code with known-structure parsed data"
)]
use std::collections::BTreeSet;
use super::{parse, Component, Components, Document, ElemId, Element, RemovalError, Span, Value};
fn parsed(s: &str) -> Document<'_> {
parse(s.into()).unwrap()
}
fn erase_spans_from_source(source: &str, spans: &[Span]) -> String {
let mut out = String::with_capacity(source.len());
let mut pos = 0;
for span in spans {
let start = usize::try_from(span.start).unwrap();
let end = usize::try_from(span.end).unwrap();
out.push_str(&source[pos..start]);
pos = end;
}
out.push_str(&source[pos..]);
out
}
fn erase_elems_from_doc(doc: &Document<'_>, ids: &BTreeSet<ElemId>) -> String {
let spans = doc.removal_spans(ids).unwrap();
let out = erase_spans_from_source(doc.source(), &spans);
parse(out.as_str().into()).expect("a removal should leave parsable JSON");
out
}
fn ids<const N: usize>(ids: [ElemId; N]) -> BTreeSet<ElemId> {
ids.into_iter().collect()
}
fn field_id(element: &Element<'_>, key: &str) -> ElemId {
element.find_field(key).unwrap().element().id()
}
fn item_id(element: &Element<'_>, index: usize) -> ElemId {
element.as_array().unwrap()[index].id()
}
#[test]
fn element_lookup_finds_the_root() {
let doc = parsed(r#"{"a": 1}"#);
assert_eq!(doc.element(doc.root().id()), Some(doc.root()));
}
#[test]
fn element_lookup_finds_a_nested_field() {
let doc = parsed(r#"{"a": {"b": [1, {"c": 2}]}}"#);
let deep = doc
.root()
.find_field("a")
.unwrap()
.element()
.find_field("b")
.unwrap()
.element()
.as_array()
.unwrap()[1]
.find_field("c")
.unwrap()
.element();
assert_eq!(doc.element(deep.id()), Some(deep));
assert_eq!(doc.element(deep.id()).unwrap().path(), "$.a.b[1].c");
}
#[test]
fn element_lookup_finds_an_array_item() {
let doc = parsed("[10, 20, 30]");
let items = doc.root().as_array().unwrap();
assert_eq!(doc.element(items[2].id()), Some(&items[2]));
}
#[test]
fn element_lookup_rejects_an_id_from_beyond_the_document() {
let doc = parsed(r#"{"a": 1}"#);
assert_eq!(doc.element(ElemId::from(99)), None);
}
#[test]
fn element_lookup_distinguishes_duplicate_keys() {
let doc = parsed(r#"{"a": 1, "a": 2}"#);
let fields = doc.root().as_object_fields().unwrap();
let second = fields[1].element();
assert_eq!(doc.element(second.id()), Some(second));
assert_eq!(
doc.element(second.id()).unwrap().value().to_string(),
"2".to_owned()
);
}
#[test]
fn removing_the_root_is_an_error() {
let doc = parsed(r#"{"a": 1}"#);
assert_eq!(
doc.removal_spans(&ids([doc.root().id()])),
Err(RemovalError::Root)
);
}
#[test]
fn removing_an_id_from_beyond_the_document_is_an_error() {
let doc = parsed(r#"{"a": 1}"#);
let unknown = ElemId::from(99);
assert_eq!(
doc.removal_spans(&ids([unknown])),
Err(RemovalError::UnknownElement(unknown))
);
}
#[test]
fn removing_nothing_leaves_the_source_alone() {
let source = r#"{"a": 1}"#;
let doc = parsed(source);
assert_eq!(
erase_elems_from_doc(&doc, &BTreeSet::new()),
source.to_owned()
);
}
#[test]
fn remove_first_of_two_fields() {
let doc = parsed(r#"{"a": 1, "b": 2}"#);
let removed = ids([field_id(doc.root(), "a")]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
r#"{"b": 2}"#.to_owned()
);
}
#[test]
fn remove_last_of_two_fields() {
let doc = parsed(r#"{"a": 1, "b": 2}"#);
let removed = ids([field_id(doc.root(), "b")]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
r#"{"a": 1}"#.to_owned()
);
}
#[test]
fn remove_a_run_of_fields_before_a_survivor() {
let doc = parsed(r#"{"a": 1, "b": 2, "c": 3}"#);
let removed = ids([field_id(doc.root(), "a"), field_id(doc.root(), "b")]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
r#"{"c": 3}"#.to_owned()
);
}
#[test]
fn remove_the_only_field() {
let doc = parsed(r#"{"a": 1}"#);
let removed = ids([field_id(doc.root(), "a")]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "{}".to_owned());
}
#[test]
fn remove_every_field() {
let doc = parsed(r#"{"a": 1, "b": 2, "c": 3}"#);
let removed = ids([
field_id(doc.root(), "a"),
field_id(doc.root(), "b"),
field_id(doc.root(), "c"),
]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "{}".to_owned());
}
#[test]
fn remove_a_middle_item() {
let doc = parsed("[1, 2, 3]");
let removed = ids([item_id(doc.root(), 1)]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "[1, 3]".to_owned());
}
#[test]
fn remove_a_last_item() {
let doc = parsed("[1, 2, 3]");
let removed = ids([item_id(doc.root(), 2)]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "[1, 2]".to_owned());
}
#[test]
fn remove_the_only_item() {
let doc = parsed("[1]");
let removed = ids([item_id(doc.root(), 0)]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "[]".to_owned());
}
#[test]
fn remove_every_item() {
let doc = parsed("[1, 2, 3]");
let removed = ids([
item_id(doc.root(), 0),
item_id(doc.root(), 1),
item_id(doc.root(), 2),
]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "[]".to_owned());
}
#[test]
fn remove_two_separated_items() {
let doc = parsed("[1, 2, 3, 4]");
let removed = ids([item_id(doc.root(), 0), item_id(doc.root(), 2)]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "[2, 4]".to_owned());
}
#[test]
fn remove_a_run_of_items_before_a_survivor() {
let doc = parsed("[1, 2, 3, 4]");
let removed = ids([item_id(doc.root(), 1), item_id(doc.root(), 2)]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "[1, 4]".to_owned());
}
#[test]
fn remove_a_run_of_items_reaching_the_bracket() {
let doc = parsed("[1, 2, 3, 4]");
let removed = ids([item_id(doc.root(), 2), item_id(doc.root(), 3)]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "[1, 2]".to_owned());
}
#[test]
fn remove_an_object_item_from_an_array() {
let doc = parsed(r#"[{"a": 1}, {"b": 2}]"#);
let removed = ids([item_id(doc.root(), 1)]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
r#"[{"a": 1}]"#.to_owned()
);
}
#[test]
fn remove_a_field_inside_a_removed_object() {
let doc = parsed(r#"{"a": {"b": null}, "c": 3}"#);
let outer = doc.root().find_field("a").unwrap().element();
let removed = ids([outer.id(), field_id(outer, "b")]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
r#"{"c": 3}"#.to_owned()
);
}
#[test]
fn remove_fields_from_a_parent_and_a_nested_object() {
let doc = parsed(r#"{"a": {"b": 1, "c": 2}, "d": 3}"#);
let inner = doc.root().find_field("a").unwrap().element();
let removed = ids([field_id(doc.root(), "d"), field_id(inner, "c")]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
r#"{"a": {"b": 1}}"#.to_owned()
);
}
#[test]
fn remove_a_field_from_pretty_json() {
let source = "{\n \"id\": \"x\",\n \"price\": null,\n \"step_size\": 1\n}";
let doc = parsed(source);
let removed = ids([field_id(doc.root(), "price")]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
"{\n \"id\": \"x\",\n \"step_size\": 1\n}".to_owned()
);
}
#[test]
fn remove_a_last_field_from_pretty_json() {
let source = "{\n \"id\": \"x\",\n \"price\": null\n}";
let doc = parsed(source);
let removed = ids([field_id(doc.root(), "price")]);
assert_eq!(
erase_elems_from_doc(&doc, &removed),
"{\n \"id\": \"x\"\n}".to_owned()
);
}
#[test]
fn remove_the_only_field_from_pretty_json() {
let source = "{\n \"price\": null\n}";
let doc = parsed(source);
let removed = ids([field_id(doc.root(), "price")]);
assert_eq!(erase_elems_from_doc(&doc, &removed), "{}".to_owned());
}
#[test]
fn item_indices_shift_after_a_removal() {
let source = r#"{"elements": [{"id": "a"}, {"id": "b"}]}"#;
let doc = parsed(source);
let elements = doc.root().find_field("elements").unwrap().element();
let removed = ids([item_id(elements, 0)]);
let out = erase_elems_from_doc(&doc, &removed);
assert_eq!(out, r#"{"elements": [{"id": "b"}]}"#.to_owned());
let after = parsed(&out);
let survivor = after
.root()
.find_field("elements")
.unwrap()
.element()
.as_array()
.unwrap()[0]
.find_field("id")
.unwrap()
.element();
assert_eq!(survivor.path(), "$.elements[0].id");
}
const SHAPES: &[&str] = &[
r#"{"a": 1}"#,
r#"{"a": 1, "b": 2}"#,
r#"{"a": 1, "b": 2, "c": 3}"#,
r#"{"a": 1, "b": 2, "c": 3, "d": 4}"#,
"[1]",
"[1, 2]",
"[1, 2, 3]",
"[1, 2, 3, 4]",
r#"{"a": {}, "b": [], "c": null}"#,
r#"{"a": {"b": 1, "c": 2}, "d": 3}"#,
r#"{"a": [1, 2], "b": [3, 4]}"#,
r#"[{"a": 1}, {"b": 2}]"#,
"[[1, 2], [3]]",
r#"{"a": [{"b": null, "c": [1]}], "d": 2}"#,
"{\n \"a\": 1,\n \"b\": [\n 2,\n 3\n ],\n \"c\": {\n \"d\": null\n }\n}",
"{\n\t\"a\": [ 1 , 2 ] ,\n\t\"b\": null\n}",
"[\n {\n \"a\": 1\n },\n {\n \"b\": 2\n }\n]",
];
const MAX_ELEMENTS: usize = 10;
#[test]
fn every_subset_of_every_shape_matches_the_pruned_oracle() {
for source in SHAPES {
let doc = parsed(source);
let candidates = ids_of(doc.root());
assert!(
candidates.len() <= MAX_ELEMENTS,
"shape holds {} elements, too many to sweep exhaustively: {source}",
candidates.len()
);
for mask in 0..(1_u32 << candidates.len()) {
let removed = subset(&candidates, mask);
let paths = paths_of(&doc, &removed);
let actual = erase_elems_from_doc(&doc, &removed);
assert_eq!(
through_serde_json(&actual),
pruned(source, &paths),
"removing {paths:?} from {source}"
);
}
}
}
fn subset(candidates: &[ElemId], mask: u32) -> BTreeSet<ElemId> {
candidates
.iter()
.enumerate()
.filter(|(position, _id)| mask & (1_u32 << position) != 0)
.map(|(_position, id)| *id)
.collect()
}
fn ids_of(root: &Element<'_>) -> Vec<ElemId> {
let mut out: Vec<ElemId> = Vec::new();
let mut stack: Vec<&Element<'_>> = vec![root];
while let Some(element) = stack.pop() {
match element.value() {
Value::Object(fields) => {
for field in fields {
out.push(field.element().id());
stack.push(field.element());
}
}
Value::Array(items) => {
for item in items {
out.push(item.id());
stack.push(item);
}
}
Value::Null | Value::True | Value::False | Value::String(_) | Value::Number(_) => {}
}
}
out
}
fn paths_of(doc: &Document<'_>, ids: &BTreeSet<ElemId>) -> Vec<String> {
ids.iter()
.map(|id| doc.element(*id).unwrap().path().into_string())
.collect()
}
fn through_serde_json(json: &str) -> String {
let value: serde_json::Value =
serde_json::from_str(json).expect("a removal should leave parsable JSON");
serde_json::to_string(&value).unwrap()
}
fn pruned(source: &str, paths: &[String]) -> String {
let mut value: serde_json::Value = serde_json::from_str(source).unwrap();
let mut sorted: Vec<&String> = paths.iter().collect();
sorted.sort();
for path in sorted.into_iter().rev() {
prune(&mut value, path);
}
serde_json::to_string(&value).unwrap()
}
fn prune(value: &mut serde_json::Value, path: &str) {
let components: Vec<Component<'_>> = Components::over(path).collect();
let Some((last, parents)) = components.split_last() else {
return;
};
let mut current = value;
for component in parents {
current = match component {
Component::Member(name) => current.get_mut(*name).unwrap(),
Component::Index(index) => current.get_mut(index.parse::<usize>().unwrap()).unwrap(),
};
}
match last {
Component::Member(name) => {
current.as_object_mut().unwrap().remove(*name);
}
Component::Index(index) => {
let index = index.parse::<usize>().unwrap();
current.as_array_mut().unwrap().remove(index);
}
}
}