Skip to main content

dataflow_rs/expr/
path.rs

1//! Paths into a context: a root plus v3's segment rules.
2//!
3//! A segment means one thing on an object and another on an array: on an
4//! object it is a key, with one leading `#` stripped; on an array it is an
5//! index, parsed from the raw segment, so `#20` is never an index. Both
6//! readings are resolved at parse time, so no write re-parses.
7//!
8//! The write rules are v3's `set_nested_value`, stated once in
9//! [`rules`](super::rules). `set`, `get` and `remove` here are hand-inlined
10//! forms of [`rules::step`](super::rules::step) for the hot path (the
11//! checkpoint and every clean read), held to
12//! [`rules::resolve`](super::rules::resolve) on every small tree and path
13//! (`state::tests::splice_property`), and `tests/it/path.rs` checks them
14//! against the corpus's model. A write either applies in full or changes
15//! nothing: containers are created only past the last existing one, and a
16//! hop that cannot continue (a scalar, a `null` under an object, a key on
17//! an array) comes before any creation.
18
19use super::rules::{self, Shape};
20use crate::base::{Error, ErrorKind, Result};
21use datavalue::OwnedDataValue;
22use std::fmt;
23use std::str::FromStr;
24use std::sync::Arc;
25
26/// The most segments a [`Path`] holds, the root's own included. The write
27/// log and the splice keep a path's resolved hops in a 64-bit mask.
28pub(crate) const MAX_PATH_SEGMENTS: usize = 64;
29
30/// One of the three context roots a path starts at.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32#[non_exhaustive]
33pub enum Root {
34    /// The message's data.
35    Data,
36    /// The message's metadata.
37    Metadata,
38    /// The message's scratch space.
39    TempData,
40}
41
42impl Root {
43    /// The root's row in [`ROOTS`](crate::base::ROOTS), written out so
44    /// reordering the enum cannot silently name another root.
45    fn row(self) -> usize {
46        match self {
47            Self::Data => 0,
48            Self::Metadata => 1,
49            Self::TempData => 2,
50        }
51    }
52
53    /// The root at `row` in [`ROOTS`](crate::base::ROOTS), if any.
54    fn from_row(row: usize) -> Option<Self> {
55        match row {
56            0 => Some(Self::Data),
57            1 => Some(Self::Metadata),
58            2 => Some(Self::TempData),
59            _ => None,
60        }
61    }
62
63    /// The root's key in the context, as a path spells it: its row of the
64    /// context's roots, which the variants follow.
65    pub fn as_str(self) -> &'static str {
66        crate::base::ROOTS[self.row()]
67    }
68}
69
70/// One path segment: its object key and, when the raw segment parses as one,
71/// its array index.
72///
73/// Built only through its constructors, which compute `hash` from `key`, so
74/// the derived equality stays consistent.
75#[derive(Clone, Debug, PartialEq, Eq)]
76pub(crate) struct Seg {
77    /// The key on an object: the raw segment with one leading `#` stripped.
78    pub(crate) key: Box<str>,
79    /// The index on an array: the raw segment parsed as `usize`.
80    pub(crate) index: Option<usize>,
81    /// [`key_hash`] of `key`, computed once, so the write log rejects a
82    /// sibling of another name with one compare. Two keys may share a hash,
83    /// so a match is always confirmed on the names.
84    pub(crate) hash: u32,
85}
86
87/// FNV-1a (32-bit) over a key's bytes: [`Seg::hash`].
88pub(crate) fn key_hash(key: &str) -> u32 {
89    key.bytes().fold(0x811c_9dc5, |h, b| {
90        (h ^ u32::from(b)).wrapping_mul(0x0100_0193)
91    })
92}
93
94impl Seg {
95    pub(crate) fn parse(raw: &str) -> Self {
96        let key = seg_key(raw);
97        Self {
98            key: key.into(),
99            index: seg_index(raw),
100            hash: key_hash(key),
101        }
102    }
103
104    /// A segment that is always an object key, never an index.
105    #[cfg(test)]
106    pub(crate) fn key(key: &str) -> Self {
107        Self {
108            key: key.into(),
109            index: None,
110            hash: key_hash(key),
111        }
112    }
113
114    /// The segment naming array element `i`: its key is `i`'s text. `i` is
115    /// at most [`MAX_ARRAY_INDEX`], or the text would parse back as a key.
116    pub(crate) fn index(i: usize) -> Self {
117        debug_assert!(i <= MAX_ARRAY_INDEX, "index {i} is past the cap");
118        let key = i.to_string();
119        Self {
120            hash: key_hash(&key),
121            key: key.into(),
122            index: Some(i),
123        }
124    }
125
126    /// Updates the index of an existing index segment in place, without
127    /// allocating a new `Box<str>` when the caller only writes into a known
128    /// array (such as a fan-out's `into` array).
129    pub(crate) fn set_index(&mut self, i: usize) {
130        debug_assert!(i <= MAX_ARRAY_INDEX, "index {i} is past the cap");
131        self.index = Some(i);
132    }
133}
134
135/// A raw segment's key on an object: one leading `#` stripped.
136fn seg_key(raw: &str) -> &str {
137    raw.strip_prefix('#').unwrap_or(raw)
138}
139
140/// The largest index a bare numeric segment names on an array. A larger
141/// number is a key, so no path can make a write pad an array past it: a
142/// trace read from outside the engine, or a computed path, could otherwise
143/// name `data.list.4294967295` and have a write allocate for every slot
144/// before it, which overflows on wasm32 and exhausts memory anywhere.
145pub const MAX_ARRAY_INDEX: usize = (1 << 20) - 1;
146
147/// A raw segment's index on an array: the raw segment parsed as `usize`, at
148/// most [`MAX_ARRAY_INDEX`], so `#20` is never one and nor is a number past
149/// the cap.
150fn seg_index(raw: &str) -> Option<usize> {
151    raw.parse().ok().filter(|&i| i <= MAX_ARRAY_INDEX)
152}
153
154/// A root plus typed segments, parsed once. A segment is a key on an object
155/// (one leading `#` stripped) and an index on an array (the raw segment parsed
156/// as `usize`, up to [`MAX_ARRAY_INDEX`]; a larger number is a key), resolved
157/// at parse.
158///
159/// `Debug` prints the dotted text, `Path("metadata.errors")`.
160#[derive(Clone, PartialEq, Eq)]
161pub struct Path {
162    root: Root,
163    /// The root's own segment first, then the segments below it: the chain the
164    /// functions below run over the context. Shared, so a clone allocates
165    /// nothing: a host call's overlay keeps one for each write.
166    segs: Arc<[Seg]>,
167    dotted: Arc<str>,
168}
169
170impl FromStr for Path {
171    type Err = Error;
172
173    fn from_str(dotted: &str) -> Result<Self> {
174        let (first, rest) = match dotted.split_once('.') {
175            Some((first, rest)) => (first, Some(rest)),
176            None => (dotted, None),
177        };
178        let root = match crate::base::ROOTS
179            .iter()
180            .position(|root| *root == first)
181            .and_then(Root::from_row)
182        {
183            Some(root) => root,
184            None => {
185                return Err(Error::new(
186                    ErrorKind::Config,
187                    format!("path `{dotted}` must start at `data`, `metadata` or `temp_data`"),
188                ));
189            }
190        };
191        let mut segs = vec![Seg::parse(first)];
192        if let Some(rest) = rest {
193            segs.extend(rest.split('.').map(Seg::parse));
194        }
195        if segs.len() > MAX_PATH_SEGMENTS {
196            return Err(Error::new(
197                ErrorKind::Config,
198                format!(
199                    "path `{dotted}` has {} segments; a path holds at most {MAX_PATH_SEGMENTS}",
200                    segs.len()
201                ),
202            ));
203        }
204        Ok(Self {
205            root,
206            segs: segs.into(),
207            dotted: Arc::from(dotted),
208        })
209    }
210}
211
212impl fmt::Display for Path {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        f.write_str(&self.dotted)
215    }
216}
217
218impl fmt::Debug for Path {
219    /// The dotted text, `Path("metadata.errors")`: the segments are the
220    /// text parsed, so they say nothing more.
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        f.debug_tuple("Path").field(&&*self.dotted).finish()
223    }
224}
225
226impl Path {
227    /// The root the path starts at.
228    pub fn root(&self) -> Root {
229        self.root
230    }
231
232    /// Does the path name a root itself, with nothing below it?
233    pub fn is_root(&self) -> bool {
234        self.segs.len() == 1
235    }
236
237    /// The segments, the root's own first.
238    pub(crate) fn segs(&self) -> &[Seg] {
239        &self.segs
240    }
241
242    /// The dotted spelling, shared: what a `Change` records as its path.
243    pub(crate) fn dotted(&self) -> &Arc<str> {
244        &self.dotted
245    }
246
247    /// The path to element `index` of the array at this path: what
248    /// `{self}.{index}` parses to, built from these segments and one more
249    /// rather than re-parsed. `None` when this path already holds the most
250    /// segments a path can.
251    #[cfg(test)]
252    pub(crate) fn index_child(&self, index: usize) -> Option<Self> {
253        if self.segs.len() >= MAX_PATH_SEGMENTS {
254            return None;
255        }
256        let segs = self.segs.iter().cloned().chain([Seg::index(index)]);
257        Some(Self {
258            root: self.root,
259            segs: segs.collect(),
260            dotted: Arc::from(format!("{}.{index}", self.dotted)),
261        })
262    }
263
264    /// The path of the first `len` segments, the root's own counted: what
265    /// the first `len` pieces of the dotted spelling parse to.
266    pub(crate) fn prefix(&self, len: usize) -> Self {
267        debug_assert!((1..=self.segs.len()).contains(&len), "a prefix of {self}");
268        let end = self
269            .dotted
270            .match_indices('.')
271            .nth(len - 1)
272            .map_or(self.dotted.len(), |(at, _)| at);
273        Self {
274            root: self.root,
275            segs: Arc::from(&self.segs[..len]),
276            dotted: Arc::from(&self.dotted[..end]),
277        }
278    }
279
280    /// The value at the path in `context`, an object holding the three roots.
281    pub fn get<'v>(&self, context: &'v OwnedDataValue) -> Option<&'v OwnedDataValue> {
282        get(context, &self.segs)
283    }
284
285    /// Assigns `value` at the path. At a root it replaces the root and accepts
286    /// only an object. A hop that cannot continue writes nothing and is `Ok`.
287    pub fn set(&self, context: &mut OwnedDataValue, value: OwnedDataValue) -> Result<()> {
288        self.check_set(&value)?;
289        self.assign(context, value);
290        Ok(())
291    }
292
293    /// Removes the value at the path and returns it. A root cannot be removed.
294    pub fn remove(&self, context: &mut OwnedDataValue) -> Result<Option<OwnedDataValue>> {
295        self.check_remove()?;
296        Ok(remove(context, &self.segs))
297    }
298
299    /// Checks a write of `value` here: a root takes only an object.
300    pub(crate) fn check_set(&self, value: &OwnedDataValue) -> Result<()> {
301        if self.is_root() && !matches!(value, OwnedDataValue::Object(_)) {
302            return Err(Error::new(
303                ErrorKind::Config,
304                format!(
305                    "`{}` is a root and takes only an object, not {}",
306                    self.dotted,
307                    kind(value)
308                ),
309            ));
310        }
311        Ok(())
312    }
313
314    /// Checks a removal here: a root cannot be removed.
315    pub(crate) fn check_remove(&self) -> Result<()> {
316        if self.is_root() {
317            return Err(Error::new(
318                ErrorKind::Config,
319                format!("`{}` is a root and cannot be removed", self.dotted),
320            ));
321        }
322        Ok(())
323    }
324
325    /// Assigns `value` at the path, reporting whether it wrote.
326    pub(crate) fn assign(&self, context: &mut OwnedDataValue, value: OwnedDataValue) -> bool {
327        set(context, &self.segs, value)
328    }
329
330    /// `map`'s append and extend at the path: the tests' owned reference for
331    /// the splice's.
332    #[cfg(test)]
333    pub(crate) fn append(
334        &self,
335        context: &mut OwnedDataValue,
336        items: Vec<OwnedDataValue>,
337    ) -> std::result::Result<bool, &'static str> {
338        append(context, &self.segs, items)
339    }
340}
341
342/// The empty container a missing hop creates: an array when the next segment
343/// is an index, an object otherwise.
344fn empty(next: &Seg) -> OwnedDataValue {
345    if next.index.is_some() {
346        OwnedDataValue::Array(Vec::new())
347    } else {
348        OwnedDataValue::Object(Vec::new())
349    }
350}
351
352/// What writing `value` at `segs` into a new, empty container produces: the
353/// owned twin of the splice's chain, for a write marked where it starts
354/// creating.
355pub(crate) fn chain(segs: &[Seg], value: OwnedDataValue) -> OwnedDataValue {
356    let Some(first) = segs.first() else {
357        return value;
358    };
359    let mut container = empty(first);
360    set(&mut container, segs, value);
361    container
362}
363
364/// One step of a read: `key` on an object, `index` on an array.
365fn hop<'v>(v: &'v OwnedDataValue, key: &str, index: Option<usize>) -> Option<&'v OwnedDataValue> {
366    match v {
367        OwnedDataValue::Object(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
368        OwnedDataValue::Array(items) => items.get(index?),
369        _ => None,
370    }
371}
372
373pub(crate) fn get<'v>(v: &'v OwnedDataValue, segs: &[Seg]) -> Option<&'v OwnedDataValue> {
374    segs.iter()
375        .try_fold(v, |v, seg| hop(v, &seg.key, seg.index))
376}
377
378/// [`get`] over a dotted path read segment by segment, by the same rules,
379/// allocating nothing: for a lookup that is not worth parsing once.
380pub(crate) fn get_dotted<'v>(v: &'v OwnedDataValue, dotted: &str) -> Option<&'v OwnedDataValue> {
381    dotted
382        .split('.')
383        .try_fold(v, |v, raw| hop(v, seg_key(raw), seg_index(raw)))
384}
385
386/// [`get`], mutably.
387pub(crate) fn get_mut<'v>(
388    mut v: &'v mut OwnedDataValue,
389    segs: &[Seg],
390) -> Option<&'v mut OwnedDataValue> {
391    for seg in segs {
392        v = match v {
393            OwnedDataValue::Object(pairs) => {
394                &mut pairs.iter_mut().find(|(k, _)| **k == *seg.key)?.1
395            }
396            OwnedDataValue::Array(items) => items.get_mut(seg.index?)?,
397            _ => return None,
398        };
399    }
400    Some(v)
401}
402
403/// Sets `value` at `segs` in `v`, returning whether it wrote.
404pub(crate) fn set(v: &mut OwnedDataValue, segs: &[Seg], value: OwnedDataValue) -> bool {
405    let Some((last, parents)) = segs.split_last() else {
406        return false;
407    };
408    let mut current = v;
409    for (i, seg) in parents.iter().enumerate() {
410        let next = &segs[i + 1];
411        current = match current {
412            OwnedDataValue::Object(pairs) => {
413                let at = match pairs.iter().position(|(k, _)| **k == *seg.key) {
414                    Some(at) => at,
415                    None => {
416                        // `String::from`, not `to_string`: a `Box<str>`
417                        // formats through `Display`.
418                        pairs.push((String::from(&*seg.key), empty(next)));
419                        pairs.len() - 1
420                    }
421                };
422                &mut pairs[at].1
423            }
424            OwnedDataValue::Array(items) => {
425                let Some(at) = seg.index else {
426                    return false;
427                };
428                if items.len() <= at {
429                    items.resize(at + 1, OwnedDataValue::Null);
430                }
431                if items[at].is_null() {
432                    items[at] = empty(next);
433                }
434                &mut items[at]
435            }
436            _ => return false,
437        };
438    }
439    match current {
440        OwnedDataValue::Object(pairs) => {
441            match pairs.iter_mut().find(|(k, _)| **k == *last.key) {
442                Some(slot) => slot.1 = value,
443                None => pairs.push((String::from(&*last.key), value)),
444            }
445            true
446        }
447        OwnedDataValue::Array(items) => {
448            let Some(at) = last.index else {
449                return false;
450            };
451            if items.len() <= at {
452                items.resize(at + 1, OwnedDataValue::Null);
453            }
454            items[at] = value;
455            true
456        }
457        _ => false,
458    }
459}
460
461/// v3's `remove_nested_value_parts`: an object key leaves with the order of
462/// the rest kept, an array element with the tail shifted down.
463pub(crate) fn remove(v: &mut OwnedDataValue, segs: &[Seg]) -> Option<OwnedDataValue> {
464    let (last, parents) = segs.split_last()?;
465    let parent = get_mut(v, parents)?;
466    match parent {
467        OwnedDataValue::Object(pairs) => {
468            let at = pairs.iter().position(|(k, _)| **k == *last.key)?;
469            Some(pairs.remove(at).1)
470        }
471        OwnedDataValue::Array(items) => {
472            let at = last.index?;
473            (at < items.len()).then(|| items.remove(at))
474        }
475        _ => None,
476    }
477}
478
479/// The kind of a value, for an error.
480pub(crate) fn kind(v: &OwnedDataValue) -> &'static str {
481    Shape::of_owned(v).describe()
482}
483
484/// `map`'s append and extend: extend the array at `segs`, or create it over a
485/// missing or `null` target. `Ok(false)` when the path is unreachable, which
486/// is treated as success with nothing written; `Err` names the kind of any
487/// other target.
488pub(crate) fn append(
489    v: &mut OwnedDataValue,
490    segs: &[Seg],
491    items: Vec<OwnedDataValue>,
492) -> std::result::Result<bool, &'static str> {
493    let target = get_mut(v, segs);
494    if rules::append_to(target.as_deref().map(Shape::of_owned))? {
495        if let Some(OwnedDataValue::Array(existing)) = target {
496            existing.extend(items);
497        }
498        return Ok(true);
499    }
500    Ok(set(v, segs, OwnedDataValue::Array(items)))
501}
502
503/// `map`'s write to a root slot: two objects merge, existing keys replaced in
504/// place and new keys appended in order; anything else replaces the slot. The
505/// owned statement of the rules the splice's `merge_root` follows, which a
506/// trace's replay applies.
507pub(crate) fn merge_root(context: &mut OwnedDataValue, key: &str, value: OwnedDataValue) {
508    if !matches!(context, OwnedDataValue::Object(_)) {
509        *context = OwnedDataValue::Object(vec![(key.to_string(), value)]);
510        return;
511    }
512    match (rules::owned_child_mut(context, rules::Hop::Key(key)), value) {
513        (Some(slot @ OwnedDataValue::Object(_)), OwnedDataValue::Object(new_pairs)) => {
514            for (k, v) in new_pairs {
515                rules::put(slot, rules::Hop::Key(&k), v);
516            }
517        }
518        (_, value) => {
519            rules::put(context, rules::Hop::Key(key), value);
520        }
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527
528    fn json(text: &str) -> OwnedDataValue {
529        serde_json::from_str(text).unwrap()
530    }
531
532    fn text(v: &OwnedDataValue) -> String {
533        serde_json::to_string(v).unwrap()
534    }
535
536    fn path(s: &str) -> Path {
537        s.parse().unwrap()
538    }
539
540    #[test]
541    fn a_path_parses_to_its_root_and_displays_back() {
542        let p = path("data.rows.#20.total");
543        assert_eq!(p.root(), Root::Data);
544        assert!(!p.is_root());
545        assert_eq!(p.to_string(), "data.rows.#20.total");
546        assert_eq!(path("temp_data.x").root(), Root::TempData);
547        assert_eq!(path("metadata.x").root().as_str(), "metadata");
548    }
549
550    #[test]
551    fn what_is_not_a_context_root_is_refused() {
552        for bad in ["", "rows.x", "#data.x", "payload", "payload.x", "Data.x"] {
553            let error = bad.parse::<Path>().unwrap_err();
554            assert_eq!(error.kind(), &ErrorKind::Config, "{bad}");
555        }
556    }
557
558    #[test]
559    fn a_bare_root_is_a_root_path() {
560        assert!(path("data").is_root());
561        assert!(path("temp_data").is_root());
562    }
563
564    #[test]
565    fn empty_segments_below_the_root_are_empty_keys() {
566        let mut ctx = json(r#"{"data":{}}"#);
567        path("data..x").set(&mut ctx, json("1")).unwrap();
568        assert_eq!(text(&ctx), r#"{"data":{"":{"x":1}}}"#);
569    }
570
571    #[test]
572    fn set_at_a_root_replaces_with_an_object_and_refuses_anything_else() {
573        let mut ctx = json(r#"{"data":{"a":1},"metadata":{}}"#);
574        path("data").set(&mut ctx, json(r#"{"b":2}"#)).unwrap();
575        assert_eq!(text(&ctx), r#"{"data":{"b":2},"metadata":{}}"#);
576        let error = path("data").set(&mut ctx, json(r#""s""#)).unwrap_err();
577        assert_eq!(error.kind(), &ErrorKind::Config);
578        assert_eq!(text(&ctx), r#"{"data":{"b":2},"metadata":{}}"#);
579    }
580
581    #[test]
582    fn remove_at_a_root_is_refused() {
583        let mut ctx = json(r#"{"data":{}}"#);
584        let error = path("data").remove(&mut ctx).unwrap_err();
585        assert_eq!(error.kind(), &ErrorKind::Config);
586    }
587
588    #[test]
589    fn a_hop_that_cannot_continue_writes_nothing_and_is_ok() {
590        let mut ctx = json(r#"{"data":{"a":5}}"#);
591        path("data.a.b").set(&mut ctx, json("1")).unwrap();
592        assert_eq!(text(&ctx), r#"{"data":{"a":5}}"#);
593        assert!(!path("data.a.b").assign(&mut ctx, json("1")));
594    }
595
596    #[test]
597    fn get_set_remove_work_below_a_root() {
598        let mut ctx = json(r#"{"data":{"rows":[{"t":1}]},"metadata":{}}"#);
599        assert_eq!(text(path("data.rows.0.t").get(&ctx).unwrap()), "1");
600        path("temp_data.x.y").set(&mut ctx, json("2")).unwrap();
601        assert_eq!(
602            text(&ctx),
603            r#"{"data":{"rows":[{"t":1}]},"metadata":{},"temp_data":{"x":{"y":2}}}"#
604        );
605        let gone = path("data.rows.0.t").remove(&mut ctx).unwrap();
606        assert_eq!(gone.map(|v| text(&v)), Some("1".into()));
607    }
608
609    #[test]
610    fn a_dotted_read_follows_the_parsed_segments_rules() {
611        let v = json(r##"{"a":{"20":"key","#b":"x","b":"hash","rows":[{"t":1},{"t":2}]}}"##);
612        for dotted in [
613            "a.20",
614            "a.#20",
615            "a.b",
616            "a.#b",
617            "a.##b",
618            "a.rows.1.t",
619            "a.rows.#1",
620            "a.rows.x",
621            "a",
622            "a.",
623            "",
624            "zz",
625        ] {
626            let segs: Vec<Seg> = dotted.split('.').map(Seg::parse).collect();
627            assert_eq!(
628                get_dotted(&v, dotted).map(text),
629                get(&v, &segs).map(text),
630                "{dotted}"
631            );
632        }
633        assert_eq!(get_dotted(&v, "a.#b").map(text), Some(r#""hash""#.into()));
634        assert_eq!(get_dotted(&v, "a.rows.#1"), None);
635    }
636
637    #[test]
638    fn a_path_holds_at_most_64_segments() {
639        let deep = |n: usize| format!("data{}", ".k".repeat(n - 1));
640        let ok: Path = deep(64).parse().expect("64 segments parse");
641        assert_eq!(ok.segs().len(), 64);
642        let err = deep(65)
643            .parse::<Path>()
644            .expect_err("65 segments are refused");
645        assert_eq!(*err.kind(), ErrorKind::Config);
646        assert!(err.to_string().contains("at most 64"), "{err}");
647    }
648
649    #[test]
650    fn an_index_stops_at_the_cap_and_a_larger_number_is_a_key() {
651        let at_cap = path(&format!("data.list.{MAX_ARRAY_INDEX}"));
652        assert_eq!(at_cap.segs().last().unwrap().index, Some(MAX_ARRAY_INDEX));
653        let past = path(&format!("data.list.{}", MAX_ARRAY_INDEX + 1));
654        assert_eq!(past.segs().last().unwrap().index, None);
655        assert_eq!(&*past.segs().last().unwrap().key, "1048576");
656
657        // On an array a key writes nothing, so the array cannot be grown past
658        // the cap; a small index pads with `null` as before.
659        let mut ctx = json(r#"{"data":{"list":[]}}"#);
660        assert!(!path("data.list.4294967295").assign(&mut ctx, json("7")));
661        assert!(!path("data.list.3000000000").assign(&mut ctx, json("7")));
662        assert_eq!(text(&ctx), r#"{"data":{"list":[]}}"#);
663        assert!(path("data.list.2").assign(&mut ctx, json("7")));
664        assert_eq!(text(&ctx), r#"{"data":{"list":[null,null,7]}}"#);
665        // Where there is no array, the number is an ordinary key.
666        assert!(path("data.obj.4294967295").assign(&mut ctx, json("1")));
667        assert_eq!(
668            get_dotted(&ctx, "data.obj.4294967295").map(text),
669            Some("1".into())
670        );
671    }
672
673    #[test]
674    fn an_index_child_is_its_dotted_spelling_parsed() {
675        for (parent, index) in [("data.rs", 0), ("temp_data.a.#b.2", 17), ("metadata", 3)] {
676            let child = path(parent).index_child(index).expect("room for the index");
677            assert_eq!(child, path(&format!("{parent}.{index}")));
678            assert_eq!(child.segs().last().unwrap().index, Some(index));
679        }
680        // A path at the most segments has no room for one more.
681        let deep = format!("data{}", ".k".repeat(63));
682        assert!(path(&deep).index_child(0).is_none());
683        let roomy = format!("data{}", ".k".repeat(62));
684        assert_eq!(path(&roomy).index_child(0).unwrap().segs().len(), 64);
685    }
686
687    #[test]
688    fn append_extends_or_creates() {
689        let mut ctx = json(r#"{"data":{"a":[1]}}"#);
690        assert_eq!(path("data.a").append(&mut ctx, vec![json("2")]), Ok(true));
691        assert_eq!(path("data.b").append(&mut ctx, vec![json("3")]), Ok(true));
692        assert_eq!(text(&ctx), r#"{"data":{"a":[1,2],"b":[3]}}"#);
693        path("data.s").set(&mut ctx, json(r#""x""#)).unwrap();
694        assert_eq!(path("data.s").append(&mut ctx, vec![]), Err("a string"));
695    }
696
697    #[test]
698    fn merge_root_merges_objects_and_replaces_the_rest() {
699        let mut ctx = json(r#"{"data":{"a":1,"b":2}}"#);
700        merge_root(&mut ctx, "data", json(r#"{"b":3,"c":4}"#));
701        assert_eq!(text(&ctx), r#"{"data":{"a":1,"b":3,"c":4}}"#);
702        merge_root(&mut ctx, "data", json("5"));
703        merge_root(&mut ctx, "metadata", json("{}"));
704        assert_eq!(text(&ctx), r#"{"data":5,"metadata":{}}"#);
705    }
706
707    #[test]
708    fn chain_builds_the_containers_a_write_would_create() {
709        let segs = &path("data.a.0").segs[1..];
710        assert_eq!(text(&chain(segs, json("1"))), r#"{"a":[1]}"#);
711        assert_eq!(text(&chain(&[], json("1"))), "1");
712    }
713}