use indexmap::IndexMap;
use std::fmt;
use crate::error::DocumentError;
pub const MAX_DEPTH: usize = 200;
pub const MAX_NODES: usize = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeId(usize);
#[derive(Debug, Clone, PartialEq)]
pub enum Scalar {
Null,
Bool(bool),
Int(num_bigint::BigInt),
Float(f64),
Str(String),
Date(String),
Time(String),
Datetime(String),
}
impl fmt::Display for Scalar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Scalar::Null => write!(f, "null"),
Scalar::Bool(b) => write!(f, "{b}"),
Scalar::Int(i) => write!(f, "{i}"),
Scalar::Float(x) => write!(f, "{x}"),
Scalar::Str(s) => write!(f, "{s:?}"),
Scalar::Date(s) | Scalar::Time(s) | Scalar::Datetime(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Int(num_bigint::BigInt),
Float(f64),
Str(String),
Date(String),
Time(String),
Datetime(String),
Array(Vec<Value>),
Object(IndexMap<String, Value>),
}
impl Value {
#[cfg(test)]
fn as_object(&self) -> Option<&IndexMap<String, Value>> {
match self {
Value::Object(m) => Some(m),
_ => None,
}
}
}
impl From<Scalar> for Value {
fn from(s: Scalar) -> Self {
match s {
Scalar::Null => Value::Null,
Scalar::Bool(b) => Value::Bool(b),
Scalar::Int(i) => Value::Int(i),
Scalar::Float(x) => Value::Float(x),
Scalar::Str(s) => Value::Str(s),
Scalar::Date(s) => Value::Date(s),
Scalar::Time(s) => Value::Time(s),
Scalar::Datetime(s) => Value::Datetime(s),
}
}
}
#[derive(Debug, Clone)]
enum NodeData {
Leaf(Scalar),
Internal(Vec<(String, NodeId)>),
}
#[derive(Debug, Clone)]
struct Entry {
data: NodeData,
depth: usize,
}
pub(crate) fn check_write_depth(depth: usize, path: &str) -> Result<(), DocumentError> {
if depth > MAX_DEPTH {
return Err(DocumentError::new(
path,
format!("nesting exceeds the maximum depth ({MAX_DEPTH})"),
));
}
Ok(())
}
fn join(path: &str, key: &str) -> String {
let is_identifier = !key.is_empty()
&& key
.chars()
.next()
.is_some_and(|c| c.is_alphabetic() || c == '_')
&& key.chars().all(|c| c.is_alphanumeric() || c == '_');
if is_identifier {
format!("{path}.{key}")
} else {
format!("{path}[\"{key}\"]")
}
}
struct ChildSpec<'a> {
path: String,
depth: usize,
value: &'a Value,
}
fn child_specs<'a>(
v: &'a Value,
path: &str,
depth: usize,
) -> Result<Vec<ChildSpec<'a>>, DocumentError> {
match v {
Value::Array(items) => {
let mut out = Vec::with_capacity(items.len());
for (i, item) in items.iter().enumerate() {
let ip = format!("{path}[{i}]");
if matches!(item, Value::Array(_)) {
return Err(DocumentError::new(
ip,
"an array of arrays has no labeled-edge form",
));
}
out.push(ChildSpec {
path: ip,
depth: depth + 1,
value: item,
});
}
Ok(out)
}
other => Ok(vec![ChildSpec {
path: path.to_string(),
depth,
value: other,
}]),
}
}
fn build_node(
arena: &mut Vec<Entry>,
value: &Value,
path: &str,
depth: usize,
) -> Result<NodeId, DocumentError> {
check_write_depth(depth, path)?;
match value {
Value::Object(map) => {
let mut edges = Vec::new();
for (k, v) in map {
let kp = join(path, k);
for spec in child_specs(v, &kp, depth + 1)? {
let cid = build_node(arena, spec.value, &spec.path, spec.depth)?;
edges.push((k.clone(), cid));
}
}
push(arena, NodeData::Internal(edges), depth, path)
}
Value::Array(_) => Err(DocumentError::new(
path,
"a bare array has no labeled-edge form (arrays appear only as a repeated field)",
)),
Value::Null => push(arena, NodeData::Leaf(Scalar::Null), depth, path),
Value::Bool(b) => push(arena, NodeData::Leaf(Scalar::Bool(*b)), depth, path),
Value::Int(i) => push(arena, NodeData::Leaf(Scalar::Int(i.clone())), depth, path),
Value::Float(x) => push(arena, NodeData::Leaf(Scalar::Float(*x)), depth, path),
Value::Date(s) => push(arena, NodeData::Leaf(Scalar::Date(s.clone())), depth, path),
Value::Time(s) => push(arena, NodeData::Leaf(Scalar::Time(s.clone())), depth, path),
Value::Datetime(s) => push(
arena,
NodeData::Leaf(Scalar::Datetime(s.clone())),
depth,
path,
),
Value::Str(s) => push(arena, NodeData::Leaf(Scalar::Str(s.clone())), depth, path),
}
}
fn push(
arena: &mut Vec<Entry>,
data: NodeData,
depth: usize,
path: &str,
) -> Result<NodeId, DocumentError> {
if arena.len() >= MAX_NODES {
return Err(DocumentError::new(
path,
format!("document exceeds the maximum node count ({MAX_NODES})"),
));
}
let id = NodeId(arena.len());
arena.push(Entry { data, depth });
Ok(id)
}
#[derive(Debug, Clone)]
pub struct Doc {
arena: Vec<Entry>,
root: NodeId,
}
impl Doc {
pub fn of(value: &Value) -> Result<Doc, DocumentError> {
let mut arena = Vec::new();
let root = build_node(&mut arena, value, "$", 0)?;
Ok(Doc { arena, root })
}
pub fn root(&self) -> Cursor<'_> {
Cursor {
doc: self,
id: self.root,
path: "$".to_string(),
}
}
fn entry(&self, id: NodeId) -> &Entry {
&self.arena[id.0]
}
pub fn add(
&mut self,
at: NodeId,
path: &str,
label: &str,
value: &Value,
) -> Result<NodeId, DocumentError> {
self.require_internal(at, path, "add")?;
let attach_depth = self.entry(at).depth;
let child_path = join(path, label);
let cid = build_node(&mut self.arena, value, &child_path, attach_depth + 1)?;
let edges = self.internal_edges_mut(at, path, "add")?;
edges.push((label.to_string(), cid));
Ok(cid)
}
pub fn set(
&mut self,
at: NodeId,
path: &str,
label: &str,
value: &Value,
) -> Result<NodeId, DocumentError> {
self.require_internal(at, path, "set")?;
let attach_depth = self.entry(at).depth;
let child_path = join(path, label);
let cid = build_node(&mut self.arena, value, &child_path, attach_depth + 1)?;
let edges = self.internal_edges_mut(at, path, "set")?;
let mut first: Option<usize> = None;
let mut kept: Vec<(String, NodeId)> = Vec::with_capacity(edges.len());
for (lbl, child) in edges.drain(..) {
if lbl == label {
if first.is_none() {
first = Some(kept.len());
kept.push((label.to_string(), cid));
}
} else {
kept.push((lbl, child));
}
}
if first.is_none() {
kept.push((label.to_string(), cid));
}
*edges = kept;
Ok(cid)
}
pub fn remove(&mut self, at: NodeId, path: &str, label: &str) -> Result<(), DocumentError> {
self.require_internal(at, path, "remove")?;
let edges = self.internal_edges_mut(at, path, "remove")?;
edges.retain(|(lbl, _)| lbl != label);
Ok(())
}
fn require_internal(&self, id: NodeId, path: &str, op: &str) -> Result<(), DocumentError> {
match self.entry(id).data {
NodeData::Internal(_) => Ok(()),
NodeData::Leaf(_) => Err(DocumentError::new(path, format!("cannot {op} on a leaf"))),
}
}
fn internal_edges_mut(
&mut self,
at: NodeId,
path: &str,
op: &str,
) -> Result<&mut Vec<(String, NodeId)>, DocumentError> {
match &mut self.arena[at.0].data {
NodeData::Internal(edges) => Ok(edges),
NodeData::Leaf(_) => Err(DocumentError::new(path, format!("cannot {op} on a leaf"))),
}
}
pub fn to_grouped(&self) -> Value {
self.grouped_at(self.root)
}
fn grouped_at(&self, id: NodeId) -> Value {
match &self.entry(id).data {
NodeData::Leaf(s) => Value::from(s.clone()),
NodeData::Internal(edges) => {
let mut counts: IndexMap<&str, usize> = IndexMap::new();
for (label, _) in edges {
*counts.entry(label.as_str()).or_insert(0) += 1;
}
let mut out: IndexMap<String, Value> = IndexMap::new();
for (label, child) in edges {
let g = self.grouped_at(*child);
if counts[label.as_str()] > 1 {
match out.get_mut(label.as_str()) {
Some(Value::Array(arr)) => arr.push(g),
_ => {
out.insert(label.clone(), Value::Array(vec![g]));
}
}
} else {
out.insert(label.clone(), g);
}
}
Value::Object(out)
}
}
}
pub fn to_data(&self) -> Value {
self.data_at(self.root)
}
fn data_at(&self, id: NodeId) -> Value {
match &self.entry(id).data {
NodeData::Leaf(s) => Value::from(s.clone()),
NodeData::Internal(edges) => {
let mut map = IndexMap::new();
for (label, child) in edges {
map.insert(label.clone(), self.data_at(*child));
}
Value::Object(map)
}
}
}
pub fn eq_doc(&self, other: &Doc) -> bool {
self.node_eq(self.root, other, other.root)
}
fn node_eq(&self, a: NodeId, other: &Doc, b: NodeId) -> bool {
match (&self.entry(a).data, &other.entry(b).data) {
(NodeData::Leaf(x), NodeData::Leaf(y)) => x == y,
(NodeData::Internal(xs), NodeData::Internal(ys)) => {
xs.len() == ys.len()
&& xs
.iter()
.zip(ys.iter())
.all(|((la, ca), (lb, cb))| la == lb && self.node_eq(*ca, other, *cb))
}
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct Cursor<'a> {
doc: &'a Doc,
id: NodeId,
pub path: String,
}
impl<'a> Cursor<'a> {
pub fn id(&self) -> NodeId {
self.id
}
pub fn is_leaf(&self) -> bool {
matches!(self.doc.entry(self.id).data, NodeData::Leaf(_))
}
pub fn value(&self) -> Result<&'a Scalar, DocumentError> {
match &self.doc.entry(self.id).data {
NodeData::Leaf(s) => Ok(s),
NodeData::Internal(_) => Err(DocumentError::new(&self.path, "not a leaf; use edges()")),
}
}
pub fn edges(&self) -> Result<Vec<(String, Cursor<'a>)>, DocumentError> {
match &self.doc.entry(self.id).data {
NodeData::Internal(edges) => {
let mut counts: IndexMap<&str, usize> = IndexMap::new();
let mut out = Vec::with_capacity(edges.len());
for (label, child) in edges {
let i = *counts.entry(label.as_str()).or_insert(0);
counts.insert(label.as_str(), i + 1);
let cp = crate::report::child_path(&self.path, label, i);
out.push((
label.clone(),
Cursor {
doc: self.doc,
id: *child,
path: cp,
},
));
}
Ok(out)
}
NodeData::Leaf(_) => Err(DocumentError::new(&self.path, "a leaf has no edges")),
}
}
pub(crate) fn internal_edges(&self) -> Result<&'a [(String, NodeId)], DocumentError> {
match &self.doc.entry(self.id).data {
NodeData::Internal(edges) => Ok(edges),
NodeData::Leaf(_) => Err(DocumentError::new(&self.path, "a leaf has no edges")),
}
}
pub(crate) fn raw_edges(&self) -> Result<Vec<(&'a str, usize, NodeId)>, DocumentError> {
match &self.doc.entry(self.id).data {
NodeData::Internal(edges) => {
let mut counts: IndexMap<&str, usize> = IndexMap::new();
let mut out = Vec::with_capacity(edges.len());
for (label, child) in edges {
let i = *counts.entry(label.as_str()).or_insert(0);
counts.insert(label.as_str(), i + 1);
out.push((label.as_str(), i, *child));
}
Ok(out)
}
NodeData::Leaf(_) => Err(DocumentError::new(&self.path, "a leaf has no edges")),
}
}
pub(crate) fn seek(&self, id: NodeId) -> Cursor<'a> {
Cursor {
doc: self.doc,
id,
path: String::new(),
}
}
pub fn labels(&self) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
if let NodeData::Internal(edges) = &self.doc.entry(self.id).data {
for (label, _) in edges {
if seen.insert(label.clone()) {
out.push(label.clone());
}
}
}
out
}
pub fn get(&self, label: &str) -> Vec<Cursor<'a>> {
self.edges()
.into_iter()
.flatten()
.filter(|(lbl, _)| lbl == label)
.map(|(_, c)| c)
.collect()
}
pub fn get_one(&self, label: &str) -> Result<Cursor<'a>, DocumentError> {
let mut cs = self.get(label);
if cs.len() != 1 {
return Err(DocumentError::new(
&self.path,
format!("expected exactly one {label:?}, found {}", cs.len()),
));
}
Ok(cs.remove(0))
}
pub fn count(&self, label: &str) -> usize {
if let NodeData::Internal(edges) = &self.doc.entry(self.id).data {
edges.iter().filter(|(lbl, _)| lbl == label).count()
} else {
0
}
}
pub fn child(&self, label: &str) -> Result<Cursor<'a>, DocumentError> {
self.get_one(label)
}
pub fn to_raw(&self) -> RawNode {
self.doc.raw_at(self.id)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RawNode {
Leaf(Scalar),
Edges(Vec<(String, RawNode)>),
}
impl Doc {
pub fn from_raw(root: RawNode) -> Result<Doc, DocumentError> {
let mut arena = Vec::new();
let root_id = push_raw(&mut arena, root, 0)?;
Ok(Doc {
arena,
root: root_id,
})
}
pub fn to_raw(&self) -> RawNode {
self.raw_at(self.root)
}
fn raw_at(&self, id: NodeId) -> RawNode {
let entry = self.entry(id);
match &entry.data {
NodeData::Leaf(s) => RawNode::Leaf(s.clone()),
NodeData::Internal(edges) => RawNode::Edges(
edges
.iter()
.map(|(label, child)| (label.clone(), self.raw_at(*child)))
.collect(),
),
}
}
}
impl Doc {
pub fn from_format(name: &str, text: &str) -> Result<Doc, crate::error::OmnistError> {
let fmt = crate::registry::get_format(name)?;
(fmt.read)(text)
}
pub fn to_format(&self, name: &str) -> Result<String, crate::error::OmnistError> {
let fmt = crate::registry::get_format(name)?;
(fmt.write)(self)
}
pub fn check_format(
&self,
name: &str,
) -> Result<crate::report::WriteReport, crate::error::OmnistError> {
let fmt = crate::registry::get_format(name)?;
match &fmt.check {
Some(check) => Ok(check(self)),
None => Err(DocumentError::new(
"$",
format!("format {name:?} has no check() -- cannot simulate a write"),
)
.into()),
}
}
}
fn push_raw(arena: &mut Vec<Entry>, node: RawNode, depth: usize) -> Result<NodeId, DocumentError> {
check_write_depth(depth, "$")?;
match node {
RawNode::Leaf(s) => push(arena, NodeData::Leaf(s), depth, "$"),
RawNode::Edges(edges) => {
let mut out = Vec::with_capacity(edges.len());
for (label, child) in edges {
let cid = push_raw(arena, child, depth + 1)?;
out.push((label, cid));
}
push(arena, NodeData::Internal(out), depth, "$")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn obj(pairs: &[(&str, Value)]) -> Value {
let mut m = IndexMap::new();
for (k, v) in pairs {
m.insert((*k).to_string(), v.clone());
}
Value::Object(m)
}
fn nest(levels: usize) -> Value {
let mut v = Value::Int((0).into());
for _ in 0..levels {
v = obj(&[("a", v)]);
}
v
}
#[test]
fn constructs_a_scalar_leaf() {
let doc = Doc::of(&Value::Int((42).into())).unwrap();
let root = doc.root();
assert!(root.is_leaf());
assert_eq!(root.value().unwrap(), &Scalar::Int((42).into()));
}
#[test]
fn constructs_an_object_as_ordered_edges() {
let v = obj(&[("b", Value::Int((1).into())), ("a", Value::Int((2).into()))]);
let doc = Doc::of(&v).unwrap();
let root = doc.root();
assert!(!root.is_leaf());
let edges = root.edges().unwrap();
let labels: Vec<&str> = edges.iter().map(|(l, _)| l.as_str()).collect();
assert_eq!(labels, vec!["b", "a"]);
}
#[test]
fn a_list_value_expands_into_repeated_edges() {
let v = obj(&[(
"member",
Value::Array(vec![
Value::Int((1).into()),
Value::Int((2).into()),
Value::Int((3).into()),
]),
)]);
let doc = Doc::of(&v).unwrap();
let root = doc.root();
assert_eq!(root.count("member"), 3);
let members = root.get("member");
let vals: Vec<&Scalar> = members.iter().map(|c| c.value().unwrap()).collect();
assert_eq!(
vals,
vec![
&Scalar::Int((1).into()),
&Scalar::Int((2).into()),
&Scalar::Int((3).into())
]
);
}
#[test]
fn a_bare_top_level_array_is_rejected() {
let err = Doc::of(&Value::Array(vec![Value::Int((1).into())])).unwrap_err();
assert!(err.message.contains("bare array"));
assert_eq!(err.path, "$");
}
#[test]
fn an_array_of_arrays_is_rejected() {
let v = obj(&[(
"a",
Value::Array(vec![Value::Array(vec![Value::Int((1).into())])]),
)]);
let err = Doc::of(&v).unwrap_err();
assert!(err.message.contains("array of arrays"));
assert_eq!(err.path, "$.a[0]");
}
#[test]
fn depth_guard_accepts_exactly_max_depth() {
let v = nest(MAX_DEPTH);
assert!(Doc::of(&v).is_ok());
}
#[test]
fn depth_guard_rejects_one_past_max_depth() {
let v = nest(MAX_DEPTH + 1);
let err = Doc::of(&v).unwrap_err();
assert!(err.message.contains("maximum depth"));
}
fn wide(n: usize) -> Value {
obj(&[("a", Value::Array(vec![Value::Int((0).into()); n]))])
}
#[test]
fn node_guard_accepts_exactly_max_nodes() {
let v = wide(MAX_NODES - 1);
assert!(Doc::of(&v).is_ok());
}
#[test]
fn node_guard_rejects_one_past_max_nodes() {
let v = wide(MAX_NODES);
let err = Doc::of(&v).unwrap_err();
assert!(err.message.contains("maximum node count"));
}
#[test]
fn an_array_value_consumes_an_extra_depth_level() {
let direct = obj(&[("a", Value::Int((1).into()))]);
let via_array = obj(&[("a", Value::Array(vec![Value::Int((1).into())]))]);
let doc_direct = Doc::of(&direct).unwrap();
let doc_array = Doc::of(&via_array).unwrap();
let leaf_direct = doc_direct.root().child("a").unwrap();
let leaf_array = doc_array.root().child("a").unwrap();
assert_eq!(doc_direct.entry(leaf_direct.id()).depth, 1);
assert_eq!(doc_array.entry(leaf_array.id()).depth, 2);
}
#[test]
fn every_tree_mutating_entry_point_enforces_the_depth_guard() {
assert!(Doc::of(&nest(MAX_DEPTH + 1)).is_err());
let mut doc = Doc::of(&obj(&[("seed", Value::Int((0).into()))])).unwrap();
let root_id = doc.root().id();
let root_path = doc.root().path.clone();
assert!(
doc.add(root_id, &root_path, "b", &nest(MAX_DEPTH + 1))
.is_err()
);
let mut doc2 = Doc::of(&obj(&[("seed", Value::Int((0).into()))])).unwrap();
let root_id2 = doc2.root().id();
let root_path2 = doc2.root().path.clone();
assert!(
doc2.set(root_id2, &root_path2, "b", &nest(MAX_DEPTH + 1))
.is_err()
);
}
#[test]
fn add_at_a_deep_cursor_accounts_for_the_cursors_own_depth() {
let mut doc = Doc::of(&nest(MAX_DEPTH)).unwrap();
let mut cursor = doc.root();
for _ in 0..190 {
cursor = cursor.child("a").unwrap();
}
assert_eq!(doc.entry(cursor.id()).depth, 190);
let id = cursor.id();
let path = cursor.path.clone();
let too_deep = nest(15);
assert!(doc.add(id, &path, "b", &too_deep).is_err());
let shallow = nest(5);
assert!(doc.set(id, &path, "b", &shallow).is_ok());
}
#[test]
fn labels_and_get_preserve_first_seen_and_insertion_order() {
let v = obj(&[
(
"z",
Value::Array(vec![Value::Int((1).into()), Value::Int((3).into())]),
),
("a", Value::Int((2).into())),
("m", Value::Int((4).into())),
]);
let doc = Doc::of(&v).unwrap();
let root = doc.root();
assert_eq!(root.labels(), vec!["z", "a", "m"]);
let z_vals: Vec<&Scalar> = root.get("z").iter().map(|c| c.value().unwrap()).collect();
assert_eq!(
z_vals,
vec![&Scalar::Int((1).into()), &Scalar::Int((3).into())]
);
}
#[test]
fn labels_and_count_on_a_leaf_are_empty() {
let doc = Doc::of(&Value::Int((1).into())).unwrap();
let root = doc.root();
assert!(root.labels().is_empty());
assert_eq!(root.count("anything"), 0);
}
#[test]
fn to_grouped_preserves_first_seen_key_order() {
let v = obj(&[
(
"z",
Value::Array(vec![Value::Int((1).into()), Value::Int((3).into())]),
),
("a", Value::Int((2).into())),
]);
let doc = Doc::of(&v).unwrap();
let grouped = doc.to_grouped();
let expected = obj(&[
(
"z",
Value::Array(vec![Value::Int((1).into()), Value::Int((3).into())]),
),
("a", Value::Int((2).into())),
]);
assert_eq!(grouped, expected);
let keys: Vec<&str> = grouped
.as_object()
.unwrap()
.keys()
.map(|s| s.as_str())
.collect();
assert_eq!(keys, vec!["z", "a"]);
}
#[test]
fn value_as_object_is_none_for_a_non_object() {
assert!(Value::Int((1).into()).as_object().is_none());
}
#[test]
fn add_appends_and_get_one_requires_exactly_one() {
let mut doc = Doc::of(&obj(&[])).unwrap();
let root_id = doc.root().id();
let root_path = doc.root().path.clone();
doc.add(root_id, &root_path, "x", &Value::Int((1).into()))
.unwrap();
doc.add(root_id, &root_path, "x", &Value::Int((2).into()))
.unwrap();
let root = doc.root();
assert_eq!(root.count("x"), 2);
assert!(root.get_one("x").is_err());
}
#[test]
fn set_replaces_all_occurrences_at_first_position() {
let mut doc = Doc::of(&obj(&[
("x", Value::Int((1).into())),
("y", Value::Int((9).into())),
("x", Value::Int((2).into())),
]))
.unwrap();
let root_id = doc.root().id();
let root_path = doc.root().path.clone();
doc.set(root_id, &root_path, "x", &Value::Int((100).into()))
.unwrap();
let root = doc.root();
let labels: Vec<String> = root.edges().unwrap().into_iter().map(|(l, _)| l).collect();
assert_eq!(labels, vec!["x", "y"]);
assert_eq!(
root.get_one("x").unwrap().value().unwrap(),
&Scalar::Int((100).into())
);
}
#[test]
fn remove_drops_every_edge_with_that_label() {
let mut doc = Doc::of(&obj(&[
("x", Value::Int((1).into())),
("x", Value::Int((2).into())),
]))
.unwrap();
let root_id = doc.root().id();
let root_path = doc.root().path.clone();
doc.remove(root_id, &root_path, "x").unwrap();
assert_eq!(doc.root().count("x"), 0);
}
#[test]
fn internal_edges_mut_rejects_a_leaf_directly() {
let mut doc = Doc::of(&Value::Int((1).into())).unwrap();
let root_id = doc.root().id();
let err = doc.internal_edges_mut(root_id, "$", "poke").unwrap_err();
assert_eq!(err.path, "$");
assert!(err.message.contains("cannot poke on a leaf"));
}
#[test]
fn mutation_on_a_leaf_is_rejected() {
let mut doc = Doc::of(&Value::Int((1).into())).unwrap();
let root_id = doc.root().id();
let root_path = doc.root().path.clone();
assert!(
doc.add(root_id, &root_path, "x", &Value::Int((1).into()))
.is_err()
);
assert!(
doc.set(root_id, &root_path, "x", &Value::Int((1).into()))
.is_err()
);
assert!(doc.remove(root_id, &root_path, "x").is_err());
}
#[test]
fn value_on_an_internal_node_is_rejected() {
let doc = Doc::of(&obj(&[("x", Value::Int((1).into()))])).unwrap();
assert!(doc.root().value().is_err());
}
#[test]
fn edges_on_a_leaf_is_rejected() {
let doc = Doc::of(&Value::Int((1).into())).unwrap();
assert!(doc.root().edges().is_err());
}
#[test]
fn raw_edges_on_a_leaf_is_rejected() {
let doc = Doc::of(&Value::Int((1).into())).unwrap();
assert!(doc.root().raw_edges().is_err());
}
#[test]
fn to_data_round_trips_structure() {
let v = obj(&[
("a", Value::Int((1).into())),
("b", Value::Str("hi".to_string())),
]);
let doc = Doc::of(&v).unwrap();
assert_eq!(doc.to_data(), v);
}
#[test]
fn to_data_round_trips_every_scalar_variant() {
let v = obj(&[
("n", Value::Null),
("b", Value::Bool(true)),
("i", Value::Int((7).into())),
("f", Value::Float(1.5)),
("s", Value::Str("hi".to_string())),
]);
let doc = Doc::of(&v).unwrap();
assert_eq!(doc.to_data(), v);
assert_eq!(doc.to_grouped(), v);
}
#[test]
fn join_quotes_a_non_identifier_key() {
let v = obj(&[(
"1bad",
Value::Array(vec![Value::Array(vec![Value::Int((1).into())])]),
)]);
let err = Doc::of(&v).unwrap_err();
assert_eq!(err.path, "$[\"1bad\"][0]");
}
#[test]
fn eq_doc_compares_structurally() {
let a = Doc::of(&obj(&[("a", Value::Int((1).into()))])).unwrap();
let b = Doc::of(&obj(&[("a", Value::Int((1).into()))])).unwrap();
let c = Doc::of(&obj(&[("a", Value::Int((2).into()))])).unwrap();
assert!(a.eq_doc(&b));
assert!(!a.eq_doc(&c));
}
#[test]
fn eq_doc_is_false_when_shapes_differ() {
let leaf = Doc::of(&Value::Int((1).into())).unwrap();
let internal = Doc::of(&obj(&[("a", Value::Int((1).into()))])).unwrap();
assert!(!leaf.eq_doc(&internal));
assert!(!internal.eq_doc(&leaf));
}
#[test]
fn scalar_display_covers_every_variant() {
assert_eq!(Scalar::Null.to_string(), "null");
assert_eq!(Scalar::Bool(true).to_string(), "true");
assert_eq!(Scalar::Int((1).into()).to_string(), "1");
assert_eq!(Scalar::Float(1.5).to_string(), "1.5");
assert_eq!(Scalar::Str("x".to_string()).to_string(), "\"x\"");
}
#[test]
fn from_raw_to_raw_round_trips_interleaved_repeated_labels() {
let raw = RawNode::Edges(vec![
("b".to_string(), RawNode::Leaf(Scalar::Int((1).into()))),
("c".to_string(), RawNode::Leaf(Scalar::Int((2).into()))),
("b".to_string(), RawNode::Leaf(Scalar::Int((3).into()))),
]);
let doc = Doc::from_raw(raw.clone()).unwrap();
assert_eq!(doc.to_raw(), raw);
let labels: Vec<String> = doc
.root()
.edges()
.unwrap()
.into_iter()
.map(|(l, _)| l)
.collect();
assert_eq!(labels, vec!["b", "c", "b"]);
}
#[test]
fn from_raw_leaf_round_trips() {
let raw = RawNode::Leaf(Scalar::Str("hi".to_string()));
let doc = Doc::from_raw(raw.clone()).unwrap();
assert!(doc.root().is_leaf());
assert_eq!(doc.to_raw(), raw);
}
#[test]
fn from_raw_enforces_the_depth_guard() {
fn nest_raw(levels: usize) -> RawNode {
let mut n = RawNode::Leaf(Scalar::Int((0).into()));
for _ in 0..levels {
n = RawNode::Edges(vec![("a".to_string(), n)]);
}
n
}
assert!(Doc::from_raw(nest_raw(MAX_DEPTH)).is_ok());
let err = Doc::from_raw(nest_raw(MAX_DEPTH + 1)).unwrap_err();
assert!(err.message.contains("maximum depth"));
}
}