#[cfg(test)]
use super::path::MAX_PATH_SEGMENTS;
use super::path::Seg;
use datavalue::{DataValue, OwnedDataValue};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Shape {
Null,
Bool,
Number,
String,
Array,
Object,
Other,
}
impl Shape {
#[inline]
pub(crate) fn of_owned(v: &OwnedDataValue) -> Self {
match v {
OwnedDataValue::Null => Self::Null,
OwnedDataValue::Bool(_) => Self::Bool,
OwnedDataValue::Number(_) => Self::Number,
OwnedDataValue::String(_) => Self::String,
OwnedDataValue::Array(_) => Self::Array,
OwnedDataValue::Object(_) => Self::Object,
#[allow(
unreachable_patterns,
reason = "the catch-all is live only with datavalue's `datetime` or `tensor` \
feature, which feature unification can turn on from another crate"
)]
_ => Self::Other,
}
}
#[inline]
pub(crate) fn of_view(v: &DataValue<'_>) -> Self {
match v {
DataValue::Null => Self::Null,
DataValue::Bool(_) => Self::Bool,
DataValue::Number(_) => Self::Number,
DataValue::String(_) => Self::String,
DataValue::Array(_) => Self::Array,
DataValue::Object(_) => Self::Object,
#[allow(
unreachable_patterns,
reason = "the catch-all is live only with datavalue's `datetime` or `tensor` \
feature, which feature unification can turn on from another crate"
)]
_ => Self::Other,
}
}
pub(crate) fn describe(self) -> &'static str {
match self {
Self::Null => "null",
Self::Bool => "a boolean",
Self::Number => "a number",
Self::String => "a string",
Self::Array => "an array",
Self::Object => "an object",
Self::Other => "a non-array value",
}
}
#[inline]
pub(crate) fn is_container(self) -> bool {
matches!(self, Self::Array | Self::Object)
}
}
pub(crate) fn container_for(next: &Seg) -> Shape {
if next.index.is_some() {
Shape::Array
} else {
Shape::Object
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Step {
Into,
Create,
Block,
}
#[inline]
pub(crate) fn step(holder: Shape, seg: &Seg, child: Option<Shape>, last: bool) -> Step {
match holder {
Shape::Object => match child {
None => Step::Create,
Some(c) if last || c.is_container() => Step::Into,
Some(_) => Step::Block,
},
Shape::Array if seg.index.is_some() => match child {
None | Some(Shape::Null) => Step::Create,
Some(c) if last || c.is_container() => Step::Into,
Some(_) => Step::Block,
},
_ => Step::Block,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Hop<'k> {
Key(&'k str),
Index(usize),
}
#[cfg(test)]
pub(crate) trait Node: Copy {
fn at(self, seg: &Seg) -> (Shape, Option<Self>);
fn shape(self) -> Shape;
}
#[cfg(test)]
impl Node for &OwnedDataValue {
#[inline]
fn at(self, seg: &Seg) -> (Shape, Option<Self>) {
match self {
OwnedDataValue::Object(pairs) => (
Shape::Object,
pairs.iter().find(|(k, _)| **k == *seg.key).map(|(_, c)| c),
),
OwnedDataValue::Array(items) => (Shape::Array, seg.index.and_then(|i| items.get(i))),
other => (Shape::of_owned(other), None),
}
}
#[inline]
fn shape(self) -> Shape {
Shape::of_owned(self)
}
}
#[cfg(test)]
impl Node for DataValue<'_> {
#[inline]
fn at(self, seg: &Seg) -> (Shape, Option<Self>) {
match self {
DataValue::Object(pairs) => (
Shape::Object,
pairs.iter().find(|(k, _)| **k == *seg.key).map(|(_, c)| *c),
),
DataValue::Array(items) => {
(Shape::Array, seg.index.and_then(|i| items.get(i).copied()))
}
other => (Shape::of_view(&other), None),
}
}
#[inline]
fn shape(self) -> Shape {
Shape::of_view(&self)
}
}
pub(crate) fn owned_child_mut<'v>(
v: &'v mut OwnedDataValue,
hop: Hop<'_>,
) -> Option<&'v mut OwnedDataValue> {
match (v, hop) {
(OwnedDataValue::Object(pairs), Hop::Key(k)) => {
pairs.iter_mut().find(|(pk, _)| pk == k).map(|(_, c)| c)
}
(OwnedDataValue::Array(items), Hop::Index(i)) => items.get_mut(i),
_ => None,
}
}
#[inline]
pub(crate) fn view_child<'v>(v: DataValue<'v>, hop: Hop<'_>) -> Option<DataValue<'v>> {
match (v, hop) {
(DataValue::Object(pairs), Hop::Key(k)) => {
pairs.iter().find(|(pk, _)| **pk == *k).map(|(_, c)| *c)
}
(DataValue::Array(items), Hop::Index(i)) => items.get(i).copied(),
_ => None,
}
}
pub(crate) fn put<'v>(
parent: &'v mut OwnedDataValue,
hop: Hop<'_>,
v: OwnedDataValue,
) -> Option<&'v mut OwnedDataValue> {
match (parent, hop) {
(OwnedDataValue::Object(pairs), Hop::Key(k)) => {
let at = match pairs.iter().position(|(pk, _)| pk == k) {
Some(at) => {
pairs[at].1 = v;
at
}
None => {
pairs.push((k.to_owned(), v));
pairs.len() - 1
}
};
Some(&mut pairs[at].1)
}
(OwnedDataValue::Array(items), Hop::Index(i)) => {
if items.len() <= i {
items.resize(i + 1, OwnedDataValue::Null);
}
items[i] = v;
Some(&mut items[i])
}
_ => None,
}
}
pub(crate) fn take(parent: &mut OwnedDataValue, hop: Hop<'_>) -> Option<OwnedDataValue> {
match (parent, hop) {
(OwnedDataValue::Object(pairs), Hop::Key(k)) => {
let at = pairs.iter().position(|(pk, _)| pk == k)?;
Some(pairs.remove(at).1)
}
(OwnedDataValue::Array(items), Hop::Index(i)) => (i < items.len()).then(|| items.remove(i)),
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Resolved {
pub(crate) kinds: u64,
pub(crate) created: Option<usize>,
}
#[cfg(test)]
pub(crate) fn resolve<N: Node>(root: N, segs: &[Seg]) -> Option<Resolved> {
if segs.is_empty() || segs.len() > MAX_PATH_SEGMENTS {
return None;
}
let mut kinds = 0u64;
let mut created = None;
let mut at = Some(root);
for (i, seg) in segs.iter().enumerate() {
let Some(node) = at else {
if seg.index.is_some() {
kinds |= 1 << i;
}
continue;
};
let (holder, child) = node.at(seg);
if holder == Shape::Array {
kinds |= 1 << i;
}
match step(holder, seg, child.map(N::shape), i + 1 == segs.len()) {
Step::Block => return None,
Step::Create => {
created.get_or_insert(i);
at = None;
}
Step::Into => at = child,
}
}
Some(Resolved { kinds, created })
}
pub(crate) fn append_to(held: Option<Shape>) -> Result<bool, &'static str> {
match held {
Some(Shape::Array) => Ok(true),
None | Some(Shape::Null) => Ok(false),
Some(other) => Err(other.describe()),
}
}
impl Seg {
pub(crate) fn may_alias(&self, other: &Self) -> bool {
self.key == other.key || self.index.is_some_and(|i| other.index == Some(i))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_step_is_v3s_rule_at_one_segment() {
let key = Seg::parse("a");
let index = Seg::parse("0");
use Shape::{Array, Null, Number, Object};
assert_eq!(step(Object, &key, None, false), Step::Create);
assert_eq!(step(Object, &key, Some(Array), false), Step::Into);
assert_eq!(step(Object, &key, Some(Null), false), Step::Block);
assert_eq!(step(Object, &key, Some(Null), true), Step::Into);
assert_eq!(step(Object, &key, Some(Number), false), Step::Block);
assert_eq!(step(Array, &key, None, true), Step::Block);
assert_eq!(step(Array, &index, None, false), Step::Create);
assert_eq!(step(Array, &index, Some(Null), true), Step::Create);
assert_eq!(step(Array, &index, Some(Number), true), Step::Into);
assert_eq!(step(Array, &index, Some(Number), false), Step::Block);
assert_eq!(step(Number, &key, None, true), Step::Block);
assert_eq!(step(Null, &index, None, true), Step::Block);
}
#[test]
fn resolve_agrees_over_the_owned_tree_and_its_view() {
let tree: OwnedDataValue =
serde_json::from_str(r#"{"a": [null, {"b": 1}, 2], "c": {"d": null}, "e": "s"}"#)
.unwrap();
let arena = bumpalo::Bump::new();
let view = tree.view_in(&arena);
for dotted in [
"a.0.x", "a.1.b", "a.1.z.0", "a.2.x", "a.5", "a.x", "c.d", "c.d.e", "e.f", "f.0.g",
"a.01", "a.+1.b",
] {
let segs: Vec<Seg> = dotted.split('.').map(Seg::parse).collect();
assert_eq!(resolve(&tree, &segs), resolve(view, &segs), "{dotted}");
}
}
#[test]
fn an_append_extends_creates_or_refuses_by_the_target() {
assert_eq!(append_to(Some(Shape::Array)), Ok(true));
assert_eq!(append_to(None), Ok(false));
assert_eq!(append_to(Some(Shape::Null)), Ok(false));
assert_eq!(append_to(Some(Shape::String)), Err("a string"));
}
}