use super::rules::{self, Shape};
use crate::base::{Error, ErrorKind, Result};
use datavalue::OwnedDataValue;
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
pub(crate) const MAX_PATH_SEGMENTS: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Root {
Data,
Metadata,
TempData,
}
impl Root {
fn row(self) -> usize {
match self {
Self::Data => 0,
Self::Metadata => 1,
Self::TempData => 2,
}
}
fn from_row(row: usize) -> Option<Self> {
match row {
0 => Some(Self::Data),
1 => Some(Self::Metadata),
2 => Some(Self::TempData),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
crate::base::ROOTS[self.row()]
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Seg {
pub(crate) key: Box<str>,
pub(crate) index: Option<usize>,
pub(crate) hash: u32,
}
pub(crate) fn key_hash(key: &str) -> u32 {
key.bytes().fold(0x811c_9dc5, |h, b| {
(h ^ u32::from(b)).wrapping_mul(0x0100_0193)
})
}
impl Seg {
pub(crate) fn parse(raw: &str) -> Self {
let key = seg_key(raw);
Self {
key: key.into(),
index: seg_index(raw),
hash: key_hash(key),
}
}
#[cfg(test)]
pub(crate) fn key(key: &str) -> Self {
Self {
key: key.into(),
index: None,
hash: key_hash(key),
}
}
pub(crate) fn index(i: usize) -> Self {
debug_assert!(i <= MAX_ARRAY_INDEX, "index {i} is past the cap");
let key = i.to_string();
Self {
hash: key_hash(&key),
key: key.into(),
index: Some(i),
}
}
pub(crate) fn set_index(&mut self, i: usize) {
debug_assert!(i <= MAX_ARRAY_INDEX, "index {i} is past the cap");
self.index = Some(i);
}
}
fn seg_key(raw: &str) -> &str {
raw.strip_prefix('#').unwrap_or(raw)
}
pub const MAX_ARRAY_INDEX: usize = (1 << 20) - 1;
fn seg_index(raw: &str) -> Option<usize> {
raw.parse().ok().filter(|&i| i <= MAX_ARRAY_INDEX)
}
#[derive(Clone, PartialEq, Eq)]
pub struct Path {
root: Root,
segs: Arc<[Seg]>,
dotted: Arc<str>,
}
impl FromStr for Path {
type Err = Error;
fn from_str(dotted: &str) -> Result<Self> {
let (first, rest) = match dotted.split_once('.') {
Some((first, rest)) => (first, Some(rest)),
None => (dotted, None),
};
let root = match crate::base::ROOTS
.iter()
.position(|root| *root == first)
.and_then(Root::from_row)
{
Some(root) => root,
None => {
return Err(Error::new(
ErrorKind::Config,
format!("path `{dotted}` must start at `data`, `metadata` or `temp_data`"),
));
}
};
let mut segs = vec![Seg::parse(first)];
if let Some(rest) = rest {
segs.extend(rest.split('.').map(Seg::parse));
}
if segs.len() > MAX_PATH_SEGMENTS {
return Err(Error::new(
ErrorKind::Config,
format!(
"path `{dotted}` has {} segments; a path holds at most {MAX_PATH_SEGMENTS}",
segs.len()
),
));
}
Ok(Self {
root,
segs: segs.into(),
dotted: Arc::from(dotted),
})
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.dotted)
}
}
impl fmt::Debug for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Path").field(&&*self.dotted).finish()
}
}
impl Path {
pub fn root(&self) -> Root {
self.root
}
pub fn is_root(&self) -> bool {
self.segs.len() == 1
}
pub(crate) fn segs(&self) -> &[Seg] {
&self.segs
}
pub(crate) fn dotted(&self) -> &Arc<str> {
&self.dotted
}
#[cfg(test)]
pub(crate) fn index_child(&self, index: usize) -> Option<Self> {
if self.segs.len() >= MAX_PATH_SEGMENTS {
return None;
}
let segs = self.segs.iter().cloned().chain([Seg::index(index)]);
Some(Self {
root: self.root,
segs: segs.collect(),
dotted: Arc::from(format!("{}.{index}", self.dotted)),
})
}
pub(crate) fn prefix(&self, len: usize) -> Self {
debug_assert!((1..=self.segs.len()).contains(&len), "a prefix of {self}");
let end = self
.dotted
.match_indices('.')
.nth(len - 1)
.map_or(self.dotted.len(), |(at, _)| at);
Self {
root: self.root,
segs: Arc::from(&self.segs[..len]),
dotted: Arc::from(&self.dotted[..end]),
}
}
pub fn get<'v>(&self, context: &'v OwnedDataValue) -> Option<&'v OwnedDataValue> {
get(context, &self.segs)
}
pub fn set(&self, context: &mut OwnedDataValue, value: OwnedDataValue) -> Result<()> {
self.check_set(&value)?;
self.assign(context, value);
Ok(())
}
pub fn remove(&self, context: &mut OwnedDataValue) -> Result<Option<OwnedDataValue>> {
self.check_remove()?;
Ok(remove(context, &self.segs))
}
pub(crate) fn check_set(&self, value: &OwnedDataValue) -> Result<()> {
if self.is_root() && !matches!(value, OwnedDataValue::Object(_)) {
return Err(Error::new(
ErrorKind::Config,
format!(
"`{}` is a root and takes only an object, not {}",
self.dotted,
kind(value)
),
));
}
Ok(())
}
pub(crate) fn check_remove(&self) -> Result<()> {
if self.is_root() {
return Err(Error::new(
ErrorKind::Config,
format!("`{}` is a root and cannot be removed", self.dotted),
));
}
Ok(())
}
pub(crate) fn assign(&self, context: &mut OwnedDataValue, value: OwnedDataValue) -> bool {
set(context, &self.segs, value)
}
#[cfg(test)]
pub(crate) fn append(
&self,
context: &mut OwnedDataValue,
items: Vec<OwnedDataValue>,
) -> std::result::Result<bool, &'static str> {
append(context, &self.segs, items)
}
}
fn empty(next: &Seg) -> OwnedDataValue {
if next.index.is_some() {
OwnedDataValue::Array(Vec::new())
} else {
OwnedDataValue::Object(Vec::new())
}
}
pub(crate) fn chain(segs: &[Seg], value: OwnedDataValue) -> OwnedDataValue {
let Some(first) = segs.first() else {
return value;
};
let mut container = empty(first);
set(&mut container, segs, value);
container
}
fn hop<'v>(v: &'v OwnedDataValue, key: &str, index: Option<usize>) -> Option<&'v OwnedDataValue> {
match v {
OwnedDataValue::Object(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
OwnedDataValue::Array(items) => items.get(index?),
_ => None,
}
}
pub(crate) fn get<'v>(v: &'v OwnedDataValue, segs: &[Seg]) -> Option<&'v OwnedDataValue> {
segs.iter()
.try_fold(v, |v, seg| hop(v, &seg.key, seg.index))
}
pub(crate) fn get_dotted<'v>(v: &'v OwnedDataValue, dotted: &str) -> Option<&'v OwnedDataValue> {
dotted
.split('.')
.try_fold(v, |v, raw| hop(v, seg_key(raw), seg_index(raw)))
}
pub(crate) fn get_mut<'v>(
mut v: &'v mut OwnedDataValue,
segs: &[Seg],
) -> Option<&'v mut OwnedDataValue> {
for seg in segs {
v = match v {
OwnedDataValue::Object(pairs) => {
&mut pairs.iter_mut().find(|(k, _)| **k == *seg.key)?.1
}
OwnedDataValue::Array(items) => items.get_mut(seg.index?)?,
_ => return None,
};
}
Some(v)
}
pub(crate) fn set(v: &mut OwnedDataValue, segs: &[Seg], value: OwnedDataValue) -> bool {
let Some((last, parents)) = segs.split_last() else {
return false;
};
let mut current = v;
for (i, seg) in parents.iter().enumerate() {
let next = &segs[i + 1];
current = match current {
OwnedDataValue::Object(pairs) => {
let at = match pairs.iter().position(|(k, _)| **k == *seg.key) {
Some(at) => at,
None => {
pairs.push((String::from(&*seg.key), empty(next)));
pairs.len() - 1
}
};
&mut pairs[at].1
}
OwnedDataValue::Array(items) => {
let Some(at) = seg.index else {
return false;
};
if items.len() <= at {
items.resize(at + 1, OwnedDataValue::Null);
}
if items[at].is_null() {
items[at] = empty(next);
}
&mut items[at]
}
_ => return false,
};
}
match current {
OwnedDataValue::Object(pairs) => {
match pairs.iter_mut().find(|(k, _)| **k == *last.key) {
Some(slot) => slot.1 = value,
None => pairs.push((String::from(&*last.key), value)),
}
true
}
OwnedDataValue::Array(items) => {
let Some(at) = last.index else {
return false;
};
if items.len() <= at {
items.resize(at + 1, OwnedDataValue::Null);
}
items[at] = value;
true
}
_ => false,
}
}
pub(crate) fn remove(v: &mut OwnedDataValue, segs: &[Seg]) -> Option<OwnedDataValue> {
let (last, parents) = segs.split_last()?;
let parent = get_mut(v, parents)?;
match parent {
OwnedDataValue::Object(pairs) => {
let at = pairs.iter().position(|(k, _)| **k == *last.key)?;
Some(pairs.remove(at).1)
}
OwnedDataValue::Array(items) => {
let at = last.index?;
(at < items.len()).then(|| items.remove(at))
}
_ => None,
}
}
pub(crate) fn kind(v: &OwnedDataValue) -> &'static str {
Shape::of_owned(v).describe()
}
pub(crate) fn append(
v: &mut OwnedDataValue,
segs: &[Seg],
items: Vec<OwnedDataValue>,
) -> std::result::Result<bool, &'static str> {
let target = get_mut(v, segs);
if rules::append_to(target.as_deref().map(Shape::of_owned))? {
if let Some(OwnedDataValue::Array(existing)) = target {
existing.extend(items);
}
return Ok(true);
}
Ok(set(v, segs, OwnedDataValue::Array(items)))
}
pub(crate) fn merge_root(context: &mut OwnedDataValue, key: &str, value: OwnedDataValue) {
if !matches!(context, OwnedDataValue::Object(_)) {
*context = OwnedDataValue::Object(vec![(key.to_string(), value)]);
return;
}
match (rules::owned_child_mut(context, rules::Hop::Key(key)), value) {
(Some(slot @ OwnedDataValue::Object(_)), OwnedDataValue::Object(new_pairs)) => {
for (k, v) in new_pairs {
rules::put(slot, rules::Hop::Key(&k), v);
}
}
(_, value) => {
rules::put(context, rules::Hop::Key(key), value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn json(text: &str) -> OwnedDataValue {
serde_json::from_str(text).unwrap()
}
fn text(v: &OwnedDataValue) -> String {
serde_json::to_string(v).unwrap()
}
fn path(s: &str) -> Path {
s.parse().unwrap()
}
#[test]
fn a_path_parses_to_its_root_and_displays_back() {
let p = path("data.rows.#20.total");
assert_eq!(p.root(), Root::Data);
assert!(!p.is_root());
assert_eq!(p.to_string(), "data.rows.#20.total");
assert_eq!(path("temp_data.x").root(), Root::TempData);
assert_eq!(path("metadata.x").root().as_str(), "metadata");
}
#[test]
fn what_is_not_a_context_root_is_refused() {
for bad in ["", "rows.x", "#data.x", "payload", "payload.x", "Data.x"] {
let error = bad.parse::<Path>().unwrap_err();
assert_eq!(error.kind(), &ErrorKind::Config, "{bad}");
}
}
#[test]
fn a_bare_root_is_a_root_path() {
assert!(path("data").is_root());
assert!(path("temp_data").is_root());
}
#[test]
fn empty_segments_below_the_root_are_empty_keys() {
let mut ctx = json(r#"{"data":{}}"#);
path("data..x").set(&mut ctx, json("1")).unwrap();
assert_eq!(text(&ctx), r#"{"data":{"":{"x":1}}}"#);
}
#[test]
fn set_at_a_root_replaces_with_an_object_and_refuses_anything_else() {
let mut ctx = json(r#"{"data":{"a":1},"metadata":{}}"#);
path("data").set(&mut ctx, json(r#"{"b":2}"#)).unwrap();
assert_eq!(text(&ctx), r#"{"data":{"b":2},"metadata":{}}"#);
let error = path("data").set(&mut ctx, json(r#""s""#)).unwrap_err();
assert_eq!(error.kind(), &ErrorKind::Config);
assert_eq!(text(&ctx), r#"{"data":{"b":2},"metadata":{}}"#);
}
#[test]
fn remove_at_a_root_is_refused() {
let mut ctx = json(r#"{"data":{}}"#);
let error = path("data").remove(&mut ctx).unwrap_err();
assert_eq!(error.kind(), &ErrorKind::Config);
}
#[test]
fn a_hop_that_cannot_continue_writes_nothing_and_is_ok() {
let mut ctx = json(r#"{"data":{"a":5}}"#);
path("data.a.b").set(&mut ctx, json("1")).unwrap();
assert_eq!(text(&ctx), r#"{"data":{"a":5}}"#);
assert!(!path("data.a.b").assign(&mut ctx, json("1")));
}
#[test]
fn get_set_remove_work_below_a_root() {
let mut ctx = json(r#"{"data":{"rows":[{"t":1}]},"metadata":{}}"#);
assert_eq!(text(path("data.rows.0.t").get(&ctx).unwrap()), "1");
path("temp_data.x.y").set(&mut ctx, json("2")).unwrap();
assert_eq!(
text(&ctx),
r#"{"data":{"rows":[{"t":1}]},"metadata":{},"temp_data":{"x":{"y":2}}}"#
);
let gone = path("data.rows.0.t").remove(&mut ctx).unwrap();
assert_eq!(gone.map(|v| text(&v)), Some("1".into()));
}
#[test]
fn a_dotted_read_follows_the_parsed_segments_rules() {
let v = json(r##"{"a":{"20":"key","#b":"x","b":"hash","rows":[{"t":1},{"t":2}]}}"##);
for dotted in [
"a.20",
"a.#20",
"a.b",
"a.#b",
"a.##b",
"a.rows.1.t",
"a.rows.#1",
"a.rows.x",
"a",
"a.",
"",
"zz",
] {
let segs: Vec<Seg> = dotted.split('.').map(Seg::parse).collect();
assert_eq!(
get_dotted(&v, dotted).map(text),
get(&v, &segs).map(text),
"{dotted}"
);
}
assert_eq!(get_dotted(&v, "a.#b").map(text), Some(r#""hash""#.into()));
assert_eq!(get_dotted(&v, "a.rows.#1"), None);
}
#[test]
fn a_path_holds_at_most_64_segments() {
let deep = |n: usize| format!("data{}", ".k".repeat(n - 1));
let ok: Path = deep(64).parse().expect("64 segments parse");
assert_eq!(ok.segs().len(), 64);
let err = deep(65)
.parse::<Path>()
.expect_err("65 segments are refused");
assert_eq!(*err.kind(), ErrorKind::Config);
assert!(err.to_string().contains("at most 64"), "{err}");
}
#[test]
fn an_index_stops_at_the_cap_and_a_larger_number_is_a_key() {
let at_cap = path(&format!("data.list.{MAX_ARRAY_INDEX}"));
assert_eq!(at_cap.segs().last().unwrap().index, Some(MAX_ARRAY_INDEX));
let past = path(&format!("data.list.{}", MAX_ARRAY_INDEX + 1));
assert_eq!(past.segs().last().unwrap().index, None);
assert_eq!(&*past.segs().last().unwrap().key, "1048576");
let mut ctx = json(r#"{"data":{"list":[]}}"#);
assert!(!path("data.list.4294967295").assign(&mut ctx, json("7")));
assert!(!path("data.list.3000000000").assign(&mut ctx, json("7")));
assert_eq!(text(&ctx), r#"{"data":{"list":[]}}"#);
assert!(path("data.list.2").assign(&mut ctx, json("7")));
assert_eq!(text(&ctx), r#"{"data":{"list":[null,null,7]}}"#);
assert!(path("data.obj.4294967295").assign(&mut ctx, json("1")));
assert_eq!(
get_dotted(&ctx, "data.obj.4294967295").map(text),
Some("1".into())
);
}
#[test]
fn an_index_child_is_its_dotted_spelling_parsed() {
for (parent, index) in [("data.rs", 0), ("temp_data.a.#b.2", 17), ("metadata", 3)] {
let child = path(parent).index_child(index).expect("room for the index");
assert_eq!(child, path(&format!("{parent}.{index}")));
assert_eq!(child.segs().last().unwrap().index, Some(index));
}
let deep = format!("data{}", ".k".repeat(63));
assert!(path(&deep).index_child(0).is_none());
let roomy = format!("data{}", ".k".repeat(62));
assert_eq!(path(&roomy).index_child(0).unwrap().segs().len(), 64);
}
#[test]
fn append_extends_or_creates() {
let mut ctx = json(r#"{"data":{"a":[1]}}"#);
assert_eq!(path("data.a").append(&mut ctx, vec![json("2")]), Ok(true));
assert_eq!(path("data.b").append(&mut ctx, vec![json("3")]), Ok(true));
assert_eq!(text(&ctx), r#"{"data":{"a":[1,2],"b":[3]}}"#);
path("data.s").set(&mut ctx, json(r#""x""#)).unwrap();
assert_eq!(path("data.s").append(&mut ctx, vec![]), Err("a string"));
}
#[test]
fn merge_root_merges_objects_and_replaces_the_rest() {
let mut ctx = json(r#"{"data":{"a":1,"b":2}}"#);
merge_root(&mut ctx, "data", json(r#"{"b":3,"c":4}"#));
assert_eq!(text(&ctx), r#"{"data":{"a":1,"b":3,"c":4}}"#);
merge_root(&mut ctx, "data", json("5"));
merge_root(&mut ctx, "metadata", json("{}"));
assert_eq!(text(&ctx), r#"{"data":5,"metadata":{}}"#);
}
#[test]
fn chain_builds_the_containers_a_write_would_create() {
let segs = &path("data.a.0").segs[1..];
assert_eq!(text(&chain(segs, json("1"))), r#"{"a":[1]}"#);
assert_eq!(text(&chain(&[], json("1"))), "1");
}
}