Skip to main content

amethystate_core/
path.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt::{self, Write as _};
4use std::hash::{Hash, Hasher};
5use std::sync::{Arc, OnceLock};
6
7/// A level's name as a path holds it.
8///
9/// Re-exported because it is part of what [`StorePath::push_shared`] takes
10/// and what a document engine hands a scan: a caller naming that type should
11/// not have to agree with this crate about a dependency to do it. Inline up to
12/// 23 bytes, which is most level names, so holding one allocates nothing.
13pub use smallvec::SmallVec;
14pub use smol_str::SmolStr;
15
16/// What divides one level from the next in a stored key.
17///
18/// Public because a backend that queries by comparison has to reason about it:
19/// the upper bound of a subtree's range is this character's successor, and
20/// spelling it out here is what makes every backend compute the same bound.
21pub const SEPARATOR: char = '.';
22
23pub(crate) const ESCAPE: char = '\\';
24
25/// Where a value lives in the store, as the levels it is under.
26///
27/// A path is built from segments and only from segments, so a name is a name:
28/// putting `"dark.mode"` in one addresses a single value with a dot in it, not
29/// two levels.
30///
31/// Two forms answer the two kinds of engine, and a path holds whichever one it
32/// arrived in. A flat engine keeps a key whole and addresses it by the joined
33/// spelling; a document engine keeps a level at a time and addresses it by the
34/// levels. The other form is built on the first reader that wants it and kept,
35/// so a path costs one of the two, and a scan that never asks for the other
36/// never pays for it.
37///
38/// Which is not a detail: both directions are taken per key of every scan.
39/// Splitting a key allocates for the list of levels; spelling levels out walks
40/// them for a separator and an escape and builds a string. Whichever a caller
41/// does not read is the whole of that cost.
42#[derive(Clone)]
43pub struct StorePath {
44    held: Held,
45}
46
47#[derive(Clone)]
48enum Held {
49    /// Written out where the code is compiled, so both forms are ready.
50    Written {
51        levels: &'static [&'static str],
52        joined: &'static str,
53    },
54
55    /// Built from its levels, which is how a document engine reads one back.
56    ///
57    /// A level comes back borrowed. The spelling is not there until something
58    /// asks - equality, order and hashing all answer from it, and so does any
59    /// flat engine - and then it is kept, and every copy of the path made
60    /// before or after shares it.
61    ///
62    /// Shared rather than held inline because a `StorePath` written into a
63    /// `const` is borrowed for the whole program, and nothing borrowed that way
64    /// may carry a cell. Behind the `Arc` the cell is reached through a pointer,
65    /// which is what the macro's declared paths need it to be.
66    Levels(Arc<Levels>),
67
68    /// Read as one key, which is how a flat engine reads one back.
69    ///
70    /// The levels are not there until something walks them, and then they are
71    /// walked out of the string and carried away by nobody. A level comes back
72    /// as a `Cow` for that reason: one holding an escaped separator is not a
73    /// run of the joined form and has to be assembled, and one without is
74    /// borrowed from it.
75    Joined { joined: SmolStr },
76
77    /// The first `levels` of another path's, sharing the list they came from.
78    ///
79    /// What [`StorePath::parent`] is for. A path's ancestors are its own levels
80    /// with the end taken off, so copying the list to shorten it is copying a
81    /// thing to keep most of it - and the shortening is asked for in loops, one
82    /// ancestor after another, where every copy is thrown away after a
83    /// comparison. Here the list is not copied at all: the count says where to
84    /// stop reading it.
85    ///
86    /// The spelling is the parent's, cut at the separator that ends the last
87    /// level kept - a run of a string the `Arc` already owns, so there is
88    /// nothing to store and no cell to carry.
89    Prefix { of: Arc<Levels>, levels: usize },
90}
91
92/// The levels of a path, and the spelling once somebody has asked for it.
93///
94/// Both halves are [`SmolStr`], which holds a string of 23 bytes or fewer
95/// inline and allocates nothing for it. That is most level names, and a good
96/// share of whole paths; anything longer goes to the heap behind a count, which
97/// is what it would have cost anyway. The level list is copied whole every time
98/// a path grows one - `push`, `join`, `parent` and `strip_prefix` all go through
99/// [`StorePath::own_levels`] - so the copies are where this is felt.
100struct Levels {
101    names: Box<[SmolStr]>,
102    joined: OnceLock<SmolStr>,
103}
104
105/// Where the first `levels` of `joined` end: the byte the separator after them
106/// sits at, or the whole length when there is no separator left to reach.
107///
108/// The escape is honoured, so a name holding one does not end a level early.
109fn end_of_level(joined: &str, levels: usize) -> usize {
110    if levels == 0 {
111        return 0;
112    }
113
114    let mut seen = 0;
115    let mut escaped = false;
116
117    for (at, ch) in joined.char_indices() {
118        match ch {
119            _ if escaped => escaped = false,
120            ESCAPE => escaped = true,
121            SEPARATOR => {
122                seen += 1;
123                if seen == levels {
124                    return at;
125                }
126            }
127            _ => {}
128        }
129    }
130
131    joined.len()
132}
133
134/// How many levels a validated joined key holds, without building any of them.
135fn count_levels(joined: &str) -> usize {
136    if joined.is_empty() {
137        return 0;
138    }
139
140    let mut levels = 1;
141    let mut escaped = false;
142    for ch in joined.chars() {
143        match ch {
144            _ if escaped => escaped = false,
145            ESCAPE => escaped = true,
146            SEPARATOR => levels += 1,
147            _ => {}
148        }
149    }
150    levels
151}
152
153/// One level of a validated joined key, borrowed unless it was escaped.
154fn level_at(joined: &str, index: usize) -> Option<Cow<'_, str>> {
155    let mut level = 0;
156    let mut start = 0;
157    let mut escaped = false;
158
159    for (at, ch) in joined.char_indices() {
160        match ch {
161            _ if escaped => escaped = false,
162            ESCAPE => escaped = true,
163            SEPARATOR => {
164                if level == index {
165                    return Some(unescape(&joined[start..at]));
166                }
167                level += 1;
168                start = at + ch.len_utf8();
169            }
170            _ => {}
171        }
172    }
173
174    (level == index && !joined.is_empty()).then(|| unescape(&joined[start..]))
175}
176
177impl StorePath {
178    /// The path that is under nothing.
179    pub const fn root() -> Self {
180        Self {
181            held: Held::Written {
182                levels: &[],
183                joined: "",
184            },
185        }
186    }
187
188    /// A path whose levels are known when the code is compiled.
189    ///
190    /// Both forms are handed over ready, so this allocates nothing: no level may
191    /// be empty, and `joined` must be exactly the joined spelling a path keeps
192    /// for `segments`, escapes and all.
193    ///
194    /// Both are checked here rather than trusted. The check is a `const fn`, so
195    /// a path written into a `const` - `StateScope::PATH`, which is where these
196    /// come from - fails to compile when the halves disagree, whether a macro or
197    /// a hand-written impl wrote them. A const panic carries no formatting, so
198    /// it names the invariant and not the level; the `#[amethystate(prefix =
199    /// ...)]` macro checks the same two things first and points at the
200    /// attribute.
201    ///
202    /// # Panics
203    ///
204    /// If any level is empty, or if `joined` is not the joined form of
205    /// `segments`. At compile time when the call is in a const context, which is
206    /// the only place it is meant to be.
207    pub const fn from_static(segments: &'static [&'static str], joined: &'static str) -> Self {
208        check_static(segments, joined);
209
210        Self {
211            held: Held::Written {
212                levels: segments,
213                joined,
214            },
215        }
216    }
217
218    /// One level named `name`, whatever `name` contains - the empty name
219    /// included.
220    ///
221    /// A level's name is the caller's to choose and every document format this
222    /// library writes lets a member be named with nothing, so a name that is
223    /// empty is a name. It is the joined spelling that cannot carry one alone:
224    /// `""` reads back as the root, so the one-level path `[""]` has no
225    /// spelling of its own and a flat text document cannot hold it.
226    pub fn segment(name: impl AsRef<str>) -> Self {
227        Self::from_checked(vec![SmolStr::new(name.as_ref())])
228    }
229
230    /// The whole of this path as one level, its spelling being the name.
231    ///
232    /// What a store with no levels is addressed by: `ui.theme` becomes one
233    /// level called `ui.theme`, and a level of the original holding a `.` keeps
234    /// the escape that tells it from a separator, so the two spell differently
235    /// here as well.
236    ///
237    /// # Panics
238    ///
239    /// If this is the root, which spells nothing and so names no level.
240    #[track_caller]
241    pub fn as_one_level(&self) -> Self {
242        Self::segment(PathRef::from(self).as_str())
243    }
244
245    /// A path out of the levels it is under, outermost first.
246    ///
247    /// No levels at all is the root, and here that is a statement rather than
248    /// an accident: an `as_root` struct's path is written out as no levels.
249    /// A list that came from data and turned out empty is somebody's filter
250    /// rather than somebody's decision, and goes through
251    /// [`StorePath::try_from_segments`], which refuses it.
252    /// [`StorePath::try_from_segments`] refuses it, because a list that came
253    /// from data and turned out empty is not something anyone said.
254    #[track_caller]
255    pub fn from_segments<I, S>(segments: I) -> Self
256    where
257        I: IntoIterator<Item = S>,
258        S: AsRef<str>,
259    {
260        match Self::try_from_segments(segments) {
261            Ok(path) => path,
262            Err(StorePathError::EmptyPath) => Self::root(),
263            Err(other) => panic!("{other}"),
264        }
265    }
266
267    /// [`StorePath::from_segments`] for levels that can turn out to be empty.
268    ///
269    /// Which is where the two differ: a written-out empty list means the root,
270    /// and a computed one that filtered down to nothing means nobody decided.
271    /// The second is refused with [`StorePathError::EmptyPath`], because it
272    /// would name the whole store, and a write through it would replace
273    /// everything.
274    pub fn try_from_segments<I, S>(segments: I) -> Result<Self, StorePathError>
275    where
276        I: IntoIterator<Item = S>,
277        S: AsRef<str>,
278    {
279        let segments = segments.into_iter();
280        let mut collected: Vec<SmolStr> = Vec::with_capacity(segments.size_hint().0);
281
282        for segment in segments {
283            collected.push(SmolStr::new(segment.as_ref()));
284        }
285
286        if collected.is_empty() {
287            return Err(StorePathError::EmptyPath);
288        }
289
290        Ok(Self::from_checked(collected))
291    }
292
293    fn from_checked(segments: Vec<SmolStr>) -> Self {
294        Self {
295            held: Held::Levels(Arc::new(Levels {
296                names: segments.into_boxed_slice(),
297                joined: OnceLock::new(),
298            })),
299        }
300    }
301
302    /// This path with one more level under it, whatever `name` contains.
303    pub fn push(&self, name: impl AsRef<str>) -> Self {
304        self.push_shared(SmolStr::new(name.as_ref()))
305    }
306
307    /// [`StorePath::push`] taking a name the caller already holds shared.
308    ///
309    /// What a document engine has: a level's name is stored as one of these,
310    /// and a scan puts a path together out of names it is holding anyway. The
311    /// other form copies the name into a fresh one per key.
312    pub fn push_shared(&self, name: SmolStr) -> Self {
313        let mut segments = self.own_levels();
314        segments.push(name);
315        Self::from_checked(segments)
316    }
317
318    /// This path with `other`'s levels under it.
319    pub fn join(&self, other: &StorePath) -> Self {
320        let mut segments = self.own_levels();
321        segments.extend(other.own_levels());
322        Self::from_checked(segments)
323    }
324
325    /// The levels, outermost first.
326    ///
327    /// Borrowed where the level is a run of the joined form, which is every
328    /// level whose name carries no separator to escape.
329    pub fn segments(&self) -> impl ExactSizeIterator<Item = Level<'_>> + '_ {
330        (0..self.len()).map(|i| self.segment_at(i).expect("counted"))
331    }
332
333    /// One level, or `None` past the end.
334    pub fn segment_at(&self, index: usize) -> Option<Level<'_>> {
335        match &self.held {
336            Held::Written { levels, .. } => levels.get(index).copied().map(Level::named),
337            Held::Levels(held) => held.names.get(index).map(|level| Level::named(level)),
338            Held::Joined { joined } => level_at(joined, index).map(Level::from_cow),
339            Held::Prefix { of, levels } => of
340                .names
341                .get(index)
342                .filter(|_| index < *levels)
343                .map(|level| Level::named(level)),
344        }
345    }
346
347    /// The whole path as one string, with the separator escaped inside names.
348    ///
349    /// This is how a document's plane names a key, where the file has to hold
350    /// something a person can read. A path that arrived as one pays nothing to
351    /// read it back; a path built from levels is spelled here, once, and holds
352    /// on to the spelling.
353    ///
354    /// Private on purpose. A path is its levels, and everything outside this
355    /// module addresses one by them: the plane reaches the spelling through
356    /// [`PathRef`], which is the type that says a joined key is meant, and
357    /// anything that only wants to *show* a path uses [`Display`]. Handing the
358    /// spelling out is how `&str` got back into signatures that had a path to
359    /// hand.
360    fn as_str(&self) -> &str {
361        match &self.held {
362            Held::Written { joined, .. } => joined,
363            Held::Levels(held) => held.joined.get_or_init(|| join(&held.names)),
364            Held::Joined { joined } => joined,
365
366            // A run of the spelling the list it came from already has, so
367            // asking for one spells the *parent* once and every ancestor of it
368            // for nothing after that. Finding where to cut is a walk of the
369            // string rather than a lookup, which is the price of not storing a
370            // second spelling per ancestor.
371            Held::Prefix { of, levels } => {
372                let whole = of.joined.get_or_init(|| join(&of.names));
373                &whole[..end_of_level(whole, *levels)]
374            }
375        }
376    }
377
378    /// The levels as their own list, for the callers that build a new path out
379    /// of them.
380    fn own_levels(&self) -> Vec<SmolStr> {
381        match &self.held {
382            Held::Written { levels, .. } => levels
383                .iter()
384                .map(|level| SmolStr::new_static(level))
385                .collect(),
386            Held::Levels(held) => held.names.to_vec(),
387            Held::Joined { joined } => (0..count_levels(joined))
388                .map(|at| SmolStr::new(&*level_at(joined, at).expect("counted")))
389                .collect(),
390            Held::Prefix { of, levels } => of.names[..*levels].to_vec(),
391        }
392    }
393
394    /// Whether this path names the whole store rather than anything in it.
395    ///
396    /// Answered per state rather than through [`len`](Self::len), because
397    /// counting the levels of a joined key walks the whole of it - and that is
398    /// the state a flat engine's keys arrive in, asked once per key of every
399    /// scan through `level_under`.
400    pub fn is_root(&self) -> bool {
401        match &self.held {
402            Held::Joined { joined } => joined.is_empty(),
403            Held::Written { levels, .. } => levels.is_empty(),
404            Held::Levels(held) => held.names.is_empty(),
405            Held::Prefix { levels, .. } => *levels == 0,
406        }
407    }
408
409    /// How many levels the path has.
410    ///
411    /// There is deliberately no `is_empty` beside it. A path of no levels is
412    /// the root - the one that names the whole store - and that is a thing
413    /// rather than the absence of one, so [`is_root`](Self::is_root) is what
414    /// asks. `is_empty` would be a second name for the same question, and the
415    /// worse of the two.
416    #[allow(clippy::len_without_is_empty)]
417    pub fn len(&self) -> usize {
418        match &self.held {
419            Held::Written { levels, .. } => levels.len(),
420            Held::Levels(held) => held.names.len(),
421            Held::Joined { joined } => count_levels(joined),
422            Held::Prefix { levels, .. } => *levels,
423        }
424    }
425
426    /// Whether every level of `prefix` starts this path.
427    ///
428    /// Compared level by level, so `ui` does not start `uix.width` - which
429    /// comparing the joined strings would say it does.
430    pub fn starts_with(&self, prefix: &StorePath) -> bool {
431        prefix.len() <= self.len() && prefix.segments().zip(self.segments()).all(|(a, b)| a == b)
432    }
433
434    /// The levels below `prefix`, or `None` when `prefix` does not start this
435    /// path.
436    pub fn strip_prefix(&self, prefix: &StorePath) -> Option<StorePath> {
437        self.starts_with(prefix)
438            .then(|| StorePath::from_checked(self.own_levels()[prefix.len()..].to_vec()))
439    }
440
441    /// Whether this subtree and `other`'s hold any key in common.
442    ///
443    /// Two subtrees are nested or apart and never half over each other, so this
444    /// is two containment tests and equality needs no arm of its own:
445    /// [`starts_with`](Self::starts_with) admits the prefix itself.
446    pub fn overlaps(&self, other: &StorePath) -> bool {
447        self.starts_with(other) || other.starts_with(self)
448    }
449
450    /// The level list to hang a prefix off, made once where there is not one
451    /// already.
452    fn shared_levels(&self) -> Arc<Levels> {
453        match &self.held {
454            Held::Levels(held) => held.clone(),
455            Held::Prefix { of, .. } => of.clone(),
456            _ => Arc::new(Levels {
457                names: self.own_levels().into_boxed_slice(),
458                joined: OnceLock::new(),
459            }),
460        }
461    }
462
463    /// The path one level up, or `None` at the root.
464    ///
465    /// Shares the level list rather than copying it short: walking up is asked
466    /// for one ancestor after another, and each answer used to be the whole
467    /// list copied to drop the end of it.
468    pub fn parent(&self) -> Option<StorePath> {
469        let levels = self.len().checked_sub(1)?;
470
471        Some(StorePath {
472            held: Held::Prefix {
473                of: self.shared_levels(),
474                levels,
475            },
476        })
477    }
478
479    /// This path and every path it sits under, nearest first, the root last.
480    ///
481    /// The set a place that holds this one can be at: a path is held at itself
482    /// or at one of its ancestors, and nowhere else. Walking it costs one level
483    /// list, made once and shared by every step - where asking for a parent and
484    /// then its parent used to copy the list per step, and throw each copy away
485    /// after a comparison.
486    pub fn upwards(&self) -> impl Iterator<Item = StorePath> + '_ {
487        let of = self.shared_levels();
488
489        (0..=self.len()).rev().map(move |levels| StorePath {
490            held: Held::Prefix {
491                of: of.clone(),
492                levels,
493            },
494        })
495    }
496
497    /// The last level, or `None` at the root.
498    pub fn name(&self) -> Option<Level<'_>> {
499        self.segment_at(self.len().checked_sub(1)?)
500    }
501
502    /// Where this path sits under `prefix`, and the level it sits at.
503    ///
504    /// The method [`level_under`] would be if it had nothing to check. That
505    /// one takes a key an engine read off a disk and can refuse it; a path has
506    /// already been through that, so this only reads.
507    ///
508    /// Which is what lets the rest of the library ask the question without
509    /// holding a key: an entry one level down and one two levels down are
510    /// different answers, and telling them apart is a map's whole business.
511    pub fn level_under(&self, prefix: &StorePath) -> Under<'_> {
512        level_below(self.as_str(), prefix.as_str(), prefix.is_root())
513    }
514
515    /// The level that follows `prefix`, read straight off the joined form.
516    ///
517    /// The same answer as `starts_with(prefix)` followed by
518    /// `segment_at(prefix.len())`, reached without splitting either path into
519    /// levels. That matters where it is used: a scan hands back a path per
520    /// stored key, and the caller wants one name from each, so splitting every
521    /// key into a level per allocation is work thrown away as soon as it is
522    /// done.
523    ///
524    /// Borrowed unless the name carries an escaped separator, which is the
525    /// only case where the level is not a run of the joined string.
526    pub fn name_under(&self, prefix: &StorePath) -> Option<Level<'_>> {
527        match level_below(self.as_str(), prefix.as_str(), prefix.is_root()) {
528            Under::Entry(name) | Under::Deeper(name) => Some(name),
529            Under::Prefix | Under::Outside => None,
530        }
531    }
532
533    /// The name `key` is stored under, below this path: the *last* level of
534    /// what remains, where [`StorePath::name_under`] is the first.
535    ///
536    /// Taking a prefix off does not touch the end, so the last level of what
537    /// remains is the last level of `key` - there is no remainder to build, and
538    /// the name is borrowed out of `key` rather than copied out of it.
539    pub fn entry_name<'k>(&self, key: &'k StorePath) -> Option<Level<'k>> {
540        if !key.starts_with(self) || key.len() == self.len() {
541            return None;
542        }
543
544        key.name()
545    }
546
547    /// Reads a joined spelling back into the path it spells.
548    ///
549    /// Only for data already on disk: a path in code is built from its
550    /// segments, so nothing else needs to parse one. Fallible because a key
551    /// this library did not write can hold a level with no name, and such a
552    /// path is not one.
553    pub fn parse_joined(joined: &str) -> Result<Self, StorePathError> {
554        validate_joined(joined)?;
555
556        Ok(Self {
557            held: Held::Joined {
558                joined: SmolStr::new(joined),
559            },
560        })
561    }
562}
563
564/// A path that borrows its joined form instead of owning one.
565///
566/// What an engine has in hand when it reads a key out of its own page: the
567/// bytes are there, in the joined spelling a path keeps, and
568/// building a `StorePath` per key would allocate once for every entry on the
569/// hot path of loading a map.
570///
571/// It carries the same checked-ness as an owned path and can be had no other
572/// way: either [`PathRef::parse`] walked the key, or it borrows a path that
573/// was walked already. So a caller holding one needs no opinion about how a
574/// path is spelled, which is the whole point - the alternative is passing
575/// `&str` and hoping every reader remembers what it is.
576#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
577pub struct PathRef<'a> {
578    joined: &'a str,
579}
580
581impl<'a> PathRef<'a> {
582    /// Reads a key an engine holds, refusing one this library did not write.
583    ///
584    /// The same walk [`StorePath::parse_joined`] makes, without the
585    /// allocation that follows it.
586    pub fn parse(joined: &'a str) -> Result<Self, StorePathError> {
587        validate_joined(joined)?;
588
589        Ok(Self { joined })
590    }
591
592    /// The whole path as one string, which is what it was built from.
593    pub fn as_str(&self) -> &'a str {
594        self.joined
595    }
596
597    pub fn is_root(&self) -> bool {
598        self.joined.is_empty()
599    }
600
601    /// Where this sits under `prefix`, and the level it sits at. See
602    /// [`StorePath::level_under`], which answers the same question of an owned
603    /// path.
604    pub fn level_under(&self, prefix: &StorePath) -> Under<'a> {
605        level_below(self.joined, prefix.as_str(), prefix.is_root())
606    }
607
608    /// The level directly under `prefix`, or `None` when `prefix` does not
609    /// hold this path. See [`StorePath::name_under`].
610    pub fn name_under(&self, prefix: &StorePath) -> Option<Level<'a>> {
611        match self.level_under(prefix) {
612            Under::Entry(name) | Under::Deeper(name) => Some(name),
613            Under::Prefix | Under::Outside => None,
614        }
615    }
616
617    /// An owned copy, for a caller that has to keep it past the borrow.
618    ///
619    /// This is where the allocation the type exists to avoid is finally paid,
620    /// so it is paid by whoever actually keeps the path.
621    pub fn to_path(&self) -> StorePath {
622        StorePath::parse_joined(self.joined).expect("a borrowed path was checked when it was made")
623    }
624}
625
626/// Borrowing a path costs nothing and checks nothing: it has been through the
627/// check already.
628impl<'a> From<&'a StorePath> for PathRef<'a> {
629    fn from(path: &'a StorePath) -> Self {
630        Self {
631            joined: path.as_str(),
632        }
633    }
634}
635
636impl fmt::Debug for PathRef<'_> {
637    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638        write!(f, "PathRef({:?})", self.joined)
639    }
640}
641
642impl fmt::Display for PathRef<'_> {
643    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644        f.write_str(self.joined)
645    }
646}
647
648impl PartialEq<StorePath> for PathRef<'_> {
649    fn eq(&self, other: &StorePath) -> bool {
650        self.joined == other.as_str()
651    }
652}
653
654/// One level of a path: a name, as the store means a name.
655///
656/// Not a run of a joined key. A name holding a separator is still one name -
657/// `dark.mode` is a level called `dark.mode`, not two - and the joined form
658/// spells it with the separator doubled. Which is the whole reason this is a
659/// type: the two are the same characters often enough that a `&str` between them
660/// says nothing, and wrong often enough that it matters.
661///
662/// Borrowed where the level is a run of the joined form, which is every level
663/// whose name carries nothing to escape, and held where it is not. A held name
664/// is a [`SmolStr`], the same way the level list of a path holds one, so a name
665/// of 23 bytes or fewer costs no allocation to keep.
666#[derive(Clone)]
667pub struct Level<'a>(Name<'a>);
668
669#[derive(Clone)]
670enum Name<'a> {
671    Borrowed(&'a str),
672    Held(SmolStr),
673}
674
675impl<'a> Level<'a> {
676    /// A name a caller wrote or a document holds, taken as the name it is.
677    ///
678    /// The entry point from outside: a level read off a document's own map, or
679    /// handed in by whoever is addressing the store. Nothing is checked here
680    /// beyond what the type says, because a name is any string at all - the
681    /// empty one included, which every format this library writes lets a
682    /// member carry.
683    pub fn named(name: &'a str) -> Self {
684        Self(Name::Borrowed(name))
685    }
686
687    /// The name as characters, for handing to a document or an engine.
688    pub fn as_str(&self) -> &str {
689        match &self.0 {
690            Name::Borrowed(name) => name,
691            Name::Held(name) => name,
692        }
693    }
694
695    /// The name with a life of its own.
696    pub fn into_owned(self) -> Level<'static> {
697        Level(match self.0 {
698            Name::Borrowed(name) => Name::Held(SmolStr::new(name)),
699            Name::Held(name) => Name::Held(name),
700        })
701    }
702
703    fn from_cow(name: Cow<'a, str>) -> Self {
704        Self(match name {
705            Cow::Borrowed(name) => Name::Borrowed(name),
706            Cow::Owned(name) => Name::Held(SmolStr::new(name)),
707        })
708    }
709}
710
711impl Level<'static> {
712    /// A name already held the way a level holds one, taken without copying it
713    /// again.
714    ///
715    /// For a caller that has spelled the name itself - see
716    /// [`SmolStrBuilder`](smol_str::SmolStrBuilder), which writes a short one
717    /// into the inline form and never reaches the heap.
718    pub fn held(name: SmolStr) -> Self {
719        Self(Name::Held(name))
720    }
721}
722
723/// By the name, whichever form holds it, so a level that was assembled answers
724/// like the one that was borrowed.
725impl PartialEq for Level<'_> {
726    fn eq(&self, other: &Self) -> bool {
727        self.as_str() == other.as_str()
728    }
729}
730
731impl Eq for Level<'_> {}
732
733impl Ord for Level<'_> {
734    fn cmp(&self, other: &Self) -> Ordering {
735        self.as_str().cmp(other.as_str())
736    }
737}
738
739impl PartialOrd for Level<'_> {
740    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
741        Some(self.cmp(other))
742    }
743}
744
745impl Hash for Level<'_> {
746    fn hash<H: Hasher>(&self, state: &mut H) {
747        self.as_str().hash(state);
748    }
749}
750
751/// A level is its name, and equality, order and hashing all answer from it -
752/// which is what lets a collection keyed by a level be looked up by one.
753impl std::borrow::Borrow<str> for Level<'_> {
754    fn borrow(&self) -> &str {
755        self.as_str()
756    }
757}
758
759impl fmt::Debug for Level<'_> {
760    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
761        write!(f, "Level({:?})", self.as_str())
762    }
763}
764
765impl fmt::Display for Level<'_> {
766    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767        f.write_str(self.as_str())
768    }
769}
770
771/// A level is a name, and [`StorePath::from_segments`] takes names.
772impl AsRef<str> for Level<'_> {
773    fn as_ref(&self) -> &str {
774        self.as_str()
775    }
776}
777
778impl PartialEq<str> for Level<'_> {
779    fn eq(&self, other: &str) -> bool {
780        self.as_str() == other
781    }
782}
783
784impl PartialEq<&str> for Level<'_> {
785    fn eq(&self, other: &&str) -> bool {
786        self.as_str() == *other
787    }
788}
789
790/// The name a document holds a child under.
791///
792/// Two different things that are the same characters. A tree's level is held
793/// under its own name; the plane holds a whole path under the spelling of it,
794/// because the plane's whole point is that a path is one key. Which of the two
795/// a caller means is said here, by the constructor it reached for, instead of
796/// being left to whoever reads the `&str` to work out - and it is the seam an
797/// obfuscated key would be introduced along, because that is exactly the day
798/// the two stop being the same characters.
799#[derive(Debug, Clone, Copy, PartialEq, Eq)]
800pub struct Stored<'a>(&'a str);
801
802impl<'a> Stored<'a> {
803    /// A level of a tree, held under its own name.
804    pub fn level(name: &'a Level<'_>) -> Self {
805        Self(name.as_str())
806    }
807
808    /// A key of the plane, held under the whole path's spelling.
809    pub fn whole(path: PathRef<'a>) -> Self {
810        Self(path.as_str())
811    }
812
813    /// A name a document read out of its own map, taken as it stands.
814    ///
815    /// The entry point from the file: what is there is what is there, and
816    /// nothing about it has been decided yet.
817    pub fn read(name: &'a str) -> Self {
818        Self(name)
819    }
820
821    /// The characters, for the format's own library to look up.
822    pub fn as_str(&self) -> &'a str {
823        self.0
824    }
825}
826
827impl fmt::Display for Stored<'_> {
828    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
829        f.write_str(self.0)
830    }
831}
832
833/// Where a key read back from a store sits relative to the prefix it was
834/// scanned from.
835#[derive(Debug, Clone, PartialEq, Eq)]
836pub enum Under<'a> {
837    /// The key is the prefix itself.
838    Prefix,
839
840    /// The key is exactly one level below, named here.
841    Entry(Level<'a>),
842
843    /// The key is more than one level below. The name is the level directly
844    /// under the prefix; the rest of the key is inside it.
845    Deeper(Level<'a>),
846
847    /// The key is not under the prefix.
848    Outside,
849}
850
851/// Where `key` sits under `prefix`, without building a path for it.
852///
853/// What a scan's caller usually wants from a key: a map keyed by the level
854/// below its own path reads every key once and asks for one name from each, so
855/// building a [`StorePath`] to answer that is a string and a walk thrown away
856/// per entry.
857///
858/// [`Under::Entry`] and [`Under::Deeper`] are separate because a map owns the
859/// level below it and nothing further: a key two levels down is somebody else's
860/// and reading it as an entry hands back the wrong value under the wrong name.
861///
862/// The key is still checked, because a key this library did not write has to
863/// be refused where it is read rather than believed.
864pub fn level_under<'a>(key: &'a str, prefix: &StorePath) -> Result<Under<'a>, StorePathError> {
865    validate_joined(key)?;
866    Ok(level_below(key, prefix.as_str(), prefix.is_root()))
867}
868
869/// The level after `head` in `whole`, both joined.
870fn level_below<'a>(whole: &'a str, head: &str, head_is_root: bool) -> Under<'a> {
871    // Nothing left once the prefix is off means the key is the prefix - but
872    // only where nothing was taken off after it. A separator was consumed
873    // below, and what follows one is a level however short it is, the empty
874    // name included.
875    let rest = if head_is_root {
876        match whole.is_empty() {
877            true => return Under::Prefix,
878            false => whole,
879        }
880    } else if whole == head {
881        return Under::Prefix;
882    } else {
883        match whole
884            .strip_prefix(head)
885            .and_then(|rest| rest.strip_prefix(SEPARATOR))
886        {
887            Some(rest) => rest,
888            None => return Under::Outside,
889        }
890    };
891
892    let mut escaped = false;
893    for (at, ch) in rest.char_indices() {
894        match ch {
895            _ if escaped => escaped = false,
896            ESCAPE => escaped = true,
897            SEPARATOR => return Under::Deeper(Level::from_cow(unescape(&rest[..at]))),
898            _ => {}
899        }
900    }
901
902    Under::Entry(Level::from_cow(unescape(rest)))
903}
904
905/// Whether a joined key is one this type could have written, without building
906/// anything to find out.
907///
908/// The same walk the split does, counting levels instead of collecting them.
909/// It runs eagerly where the split does not, because a key that will not parse
910/// has to be refused where it is read rather than wherever someone first asks
911/// for its levels.
912fn validate_joined(joined: &str) -> Result<(), StorePathError> {
913    let mut escaped = false;
914
915    for ch in joined.chars() {
916        match ch {
917            _ if escaped => {
918                if ch != SEPARATOR && ch != ESCAPE {
919                    return Err(StorePathError::DanglingEscape);
920                }
921                escaped = false;
922            }
923            ESCAPE => escaped = true,
924            _ => {}
925        }
926    }
927
928    match escaped {
929        true => Err(StorePathError::DanglingEscape),
930        false => Ok(()),
931    }
932}
933
934/// One level as it was written, borrowing when nothing was escaped.
935fn unescape(level: &str) -> Cow<'_, str> {
936    if !level.contains(ESCAPE) {
937        return Cow::Borrowed(level);
938    }
939
940    let mut out = String::with_capacity(level.len());
941    let mut escaped = false;
942    for ch in level.chars() {
943        match ch {
944            _ if escaped => {
945                out.push(ch);
946                escaped = false;
947            }
948            ESCAPE => escaped = true,
949            _ => out.push(ch),
950        }
951    }
952    Cow::Owned(out)
953}
954
955/// Why a set of segments is not a path.
956#[derive(Debug, Clone, PartialEq, Eq)]
957pub enum StorePathError {
958    /// An escape that escapes nothing. No key this type wrote holds one, and
959    /// reading it leniently would let two different keys name one path.
960    DanglingEscape,
961
962    /// No levels at all.
963    ///
964    /// A path is built from a list, and a list computed at run time can come
965    /// out empty - a filter that removed everything, a split of an empty
966    /// string. Such a list names the whole store, and a write through it would
967    /// replace all of it in code that never mentions the root.
968    ///
969    /// [`StorePath::root`] is how to say it on purpose.
970    EmptyPath,
971}
972
973impl fmt::Display for StorePathError {
974    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
975        match self {
976            StorePathError::DanglingEscape => {
977                f.write_str("an escape must be followed by a separator or another escape")
978            }
979            StorePathError::EmptyPath => f.write_str(
980                "a path of no levels names the whole store - say `StorePath::root()` to mean it",
981            ),
982        }
983    }
984}
985
986impl std::error::Error for StorePathError {}
987
988/// What a call can be given where a path is wanted.
989///
990/// A list of levels, or a path already built. Deliberately not `&str`: a string
991/// is a name, and letting one stand in for a path is the confusion this type
992/// exists to end - `store.get(["ui", "width"])` and `store.get("ui.width")`
993/// would otherwise look alike and mean different things.
994#[diagnostic::on_unimplemented(
995    message = "a path is a list of levels, and `{Self}` is not one",
996    label = "expected something like `[\"ui\", \"width\"]`",
997    note = "a string is a name, not a path: `\"ui.width\"` would be one level \
998            whose name holds a separator, which is somewhere else entirely. \
999            Spell the nesting out, or use `StorePath::parse_joined` to read a \
1000            path out of a string on purpose",
1001    note = "addressing one name rather than a path is what `Kv` is for, and it \
1002            does take `&str`"
1003)]
1004pub trait IntoStorePath {
1005    fn into_store_path(self) -> Result<StorePath, StorePathError>;
1006}
1007
1008impl IntoStorePath for StorePath {
1009    fn into_store_path(self) -> Result<StorePath, StorePathError> {
1010        Ok(self)
1011    }
1012}
1013
1014impl IntoStorePath for &StorePath {
1015    fn into_store_path(self) -> Result<StorePath, StorePathError> {
1016        Ok(self.clone())
1017    }
1018}
1019
1020impl<S: AsRef<str>, const N: usize> IntoStorePath for [S; N] {
1021    fn into_store_path(self) -> Result<StorePath, StorePathError> {
1022        StorePath::try_from_segments(self)
1023    }
1024}
1025
1026impl<S: AsRef<str>> IntoStorePath for &[S] {
1027    fn into_store_path(self) -> Result<StorePath, StorePathError> {
1028        StorePath::try_from_segments(self)
1029    }
1030}
1031
1032impl<S: AsRef<str>> IntoStorePath for Vec<S> {
1033    fn into_store_path(self) -> Result<StorePath, StorePathError> {
1034        StorePath::try_from_segments(self)
1035    }
1036}
1037
1038const SEPARATOR_BYTE: u8 = SEPARATOR as u8;
1039const ESCAPE_BYTE: u8 = ESCAPE as u8;
1040
1041/// Ends a level, and occurs nowhere else: a level's own `0x00` is written as
1042/// [`STANDS_IN`] `0x01`, so an encoded level holds no byte below `0x01` at all.
1043///
1044/// That is what makes a subtree a byte prefix and nothing more. An escape that
1045/// merely *followed* the terminator would not: `["ui\0x"]` would begin with the
1046/// encoding of `["ui"]` and read as a key under it.
1047const ENDS_A_LEVEL: u8 = 0x00;
1048
1049/// Stands in front of a byte the encoding needs back, and is written the same
1050/// way itself.
1051///
1052/// `0x00` becomes `0x01 0x01` and `0x01` becomes `0x01 0x02`, which keeps the
1053/// order the bytes were in: both land under `0x02`, where they belong, and
1054/// above each other in the right order.
1055const STANDS_IN: u8 = 0x01;
1056
1057/// A path as a flat engine stores it: every level, in order, each one ended.
1058///
1059/// The engines that hold a path as one key had no way to see where a level
1060/// ended, so the key carried the levels joined by a separator and every name
1061/// escaped to keep the separator out of it - and escaping does not preserve
1062/// order, which is why the names had to be compared through a function of their
1063/// own and why a scan's upper bound had to be guessed at and then filtered.
1064///
1065/// A terminator that cannot occur inside a level says the same thing without
1066/// any of that. Byte order over these keys *is* the order of the level lists
1067/// they came from, so a subtree is a byte prefix, exactly and with nothing left
1068/// over to check.
1069/// Held inline up to the length a path of the depth this library is built for
1070/// reaches: eight levels with names of ordinary length come to about sixty
1071/// bytes, and a key is built once per read and once per write rather than once
1072/// per scan. Past that it spills to the heap and costs what any buffer would.
1073pub type KeyBytes = SmallVec<[u8; 64]>;
1074
1075#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1076pub struct Key(KeyBytes);
1077
1078impl StorePath {
1079    /// This path as a key.
1080    pub fn key(&self) -> Key {
1081        let mut out = KeyBytes::new();
1082        self.write_key(&mut out);
1083        Key(out)
1084    }
1085
1086    /// The same, into a buffer the caller keeps between keys.
1087    pub fn write_key(&self, out: &mut KeyBytes) {
1088        for level in self.segments() {
1089            for &byte in level.as_str().as_bytes() {
1090                match byte {
1091                    ENDS_A_LEVEL => out.extend_from_slice(&[STANDS_IN, STANDS_IN]),
1092                    STANDS_IN => out.extend_from_slice(&[STANDS_IN, STANDS_IN + 1]),
1093                    byte => out.push(byte),
1094                }
1095            }
1096            out.push(ENDS_A_LEVEL);
1097        }
1098    }
1099}
1100
1101impl Key {
1102    /// The root, which every key is under and which is no bytes at all.
1103    pub fn root() -> Self {
1104        Key(KeyBytes::new())
1105    }
1106
1107    /// A key as a store hands it back.
1108    ///
1109    /// Takes the bytes as they are; [`Key::path`] is where a key that this type
1110    /// did not write is refused, because that is where the answer has somewhere
1111    /// to go.
1112    pub fn from_bytes(bytes: &[u8]) -> Self {
1113        Key(KeyBytes::from_slice(bytes))
1114    }
1115
1116    pub fn as_bytes(&self) -> &[u8] {
1117        &self.0
1118    }
1119
1120    pub fn is_root(&self) -> bool {
1121        self.0.is_empty()
1122    }
1123
1124    /// The path this key spells.
1125    ///
1126    /// Refuses what this type does not write: bytes that are not text, a level
1127    /// left unterminated, and an escape that escapes nothing. Reading any of
1128    /// those leniently would let two keys name one path.
1129    pub fn path(&self) -> Result<StorePath, StorePathError> {
1130        let mut names: Vec<SmolStr> = Vec::new();
1131        let mut level = KeyBytes::new();
1132        let mut at = 0;
1133
1134        while at < self.0.len() {
1135            let byte = self.0[at];
1136            at += 1;
1137
1138            match byte {
1139                ENDS_A_LEVEL => {
1140                    let Ok(name) = std::str::from_utf8(&level) else {
1141                        return Err(not_a_key_this_wrote());
1142                    };
1143
1144                    names.push(SmolStr::new(name));
1145                    level.clear();
1146                }
1147                STANDS_IN => {
1148                    let held = self.0.get(at).copied();
1149                    at += 1;
1150
1151                    match held {
1152                        Some(STANDS_IN) => level.push(ENDS_A_LEVEL),
1153                        Some(byte) if byte == STANDS_IN + 1 => level.push(STANDS_IN),
1154                        _ => return Err(not_a_key_this_wrote()),
1155                    }
1156                }
1157                byte => level.push(byte),
1158            }
1159        }
1160
1161        if !level.is_empty() {
1162            return Err(not_a_key_this_wrote());
1163        }
1164
1165        match names.is_empty() {
1166            true => Ok(StorePath::root()),
1167            false => Ok(StorePath::from_checked(names)),
1168        }
1169    }
1170
1171    /// Whether this key names `prefix` or something under it.
1172    ///
1173    /// One test, because a level's terminator cannot occur inside a level: a
1174    /// key under `pot` starts with `pot\0`, `potato` does not, and there is no
1175    /// third answer for a caller to filter afterwards.
1176    pub fn under(&self, prefix: &Key) -> bool {
1177        self.0.starts_with(&prefix.0)
1178    }
1179
1180    /// The half-open range this key's subtree occupies, both ends exact. The
1181    /// root has no top.
1182    ///
1183    /// The top is this key with its last byte raised, which reaches everything
1184    /// under it because that byte is a terminator - the lowest there is, so
1185    /// raising it lands past every level the key could hold and short of its
1186    /// next sibling.
1187    pub fn subtree(&self) -> (&[u8], Option<KeyBytes>) {
1188        let mut top = self.0.clone();
1189
1190        while let Some(last) = top.pop() {
1191            if last < u8::MAX {
1192                top.push(last + 1);
1193                return (&self.0, Some(top));
1194            }
1195        }
1196
1197        (&[], None)
1198    }
1199}
1200
1201/// Refusals a decode makes, kept out of line.
1202///
1203/// Every key a store hands back is one this library wrote, so these arms are
1204/// taken by a hand edit or an older build and by nothing else - and a decode
1205/// runs once per key of every scan. `#[cold]` is what says so on a stable
1206/// compiler: `hint::cold_path` is not one yet.
1207#[cold]
1208#[inline(never)]
1209fn not_a_key_this_wrote() -> StorePathError {
1210    StorePathError::DanglingEscape
1211}
1212
1213impl fmt::Debug for Key {
1214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1215        match self.path() {
1216            Ok(path) => write!(f, "Key({path})"),
1217            Err(_) => write!(f, "Key({:?})", &self.0),
1218        }
1219    }
1220}
1221
1222impl From<&StorePath> for Key {
1223    fn from(path: &StorePath) -> Self {
1224        path.key()
1225    }
1226}
1227
1228const fn check_static(segments: &[&str], joined: &str) {
1229    if has_empty_segment(segments) {
1230        panic!("a path segment cannot be empty");
1231    }
1232
1233    if !joins_to(segments, joined) {
1234        panic!(
1235            "the joined form of a static path must be what StorePath::as_str writes for its segments"
1236        );
1237    }
1238}
1239
1240const fn has_empty_segment(segments: &[&str]) -> bool {
1241    let mut at = 0;
1242
1243    while at < segments.len() {
1244        if segments[at].is_empty() {
1245            return true;
1246        }
1247        at += 1;
1248    }
1249
1250    false
1251}
1252
1253const fn joins_to(segments: &[&str], joined: &str) -> bool {
1254    let key = joined.as_bytes();
1255    let mut at = 0;
1256    let mut i = 0;
1257
1258    while at < segments.len() {
1259        if at > 0 {
1260            if i >= key.len() || key[i] != SEPARATOR_BYTE {
1261                return false;
1262            }
1263            i += 1;
1264        }
1265
1266        let segment = segments[at].as_bytes();
1267        let mut j = 0;
1268
1269        while j < segment.len() {
1270            let byte = segment[j];
1271
1272            if byte == SEPARATOR_BYTE || byte == ESCAPE_BYTE {
1273                if i >= key.len() || key[i] != ESCAPE_BYTE {
1274                    return false;
1275                }
1276                i += 1;
1277            }
1278
1279            if i >= key.len() || key[i] != byte {
1280                return false;
1281            }
1282            i += 1;
1283            j += 1;
1284        }
1285
1286        at += 1;
1287    }
1288
1289    i == key.len()
1290}
1291
1292fn join(segments: &[SmolStr]) -> SmolStr {
1293    let mut out = String::new();
1294
1295    for (i, segment) in segments.iter().enumerate() {
1296        if i > 0 {
1297            out.push(SEPARATOR);
1298        }
1299        for ch in segment.chars() {
1300            if ch == SEPARATOR || ch == ESCAPE {
1301                out.push(ESCAPE);
1302            }
1303            out.push(ch);
1304        }
1305    }
1306
1307    SmolStr::new(out)
1308}
1309
1310/// Equal paths are the ones that address the same place, which is the ones
1311/// holding the same levels - the same question [`Ord`] answers, asked of the
1312/// same form, so the two cannot drift apart.
1313impl PartialEq for StorePath {
1314    fn eq(&self, other: &Self) -> bool {
1315        self.len() == other.len() && self.segments().eq(other.segments())
1316    }
1317}
1318
1319impl Eq for StorePath {}
1320
1321/// The order a store lists in: by the levels, outermost first.
1322///
1323/// The flat engines range over [`Key`], whose byte order is this order, so
1324/// anything sorting paths for itself agrees with them by construction.
1325///
1326/// Not by the joined spelling. Joining puts a separator between levels and
1327/// escapes it inside them, and a name holding a byte below that separator then
1328/// sorts on the wrong side of a boundary: `["a!"]` comes after `["a", "b"]` by
1329/// levels and before it by spelling. The levels are what a path *is*, and
1330/// [`Key`] encodes them so that a flat engine's byte order says the same thing.
1331impl Ord for StorePath {
1332    fn cmp(&self, other: &Self) -> Ordering {
1333        self.segments().cmp(other.segments())
1334    }
1335}
1336
1337impl PartialOrd for StorePath {
1338    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1339        Some(self.cmp(other))
1340    }
1341}
1342
1343/// By the levels, so it agrees with `Eq` however the path arrived.
1344///
1345/// `str`'s own `Hash` ends what it writes, so two levels cannot run together
1346/// into the hash of one - `["a", "b"]` and `["ab"]` are different paths and
1347/// hash differently.
1348impl Hash for StorePath {
1349    fn hash<H: Hasher>(&self, state: &mut H) {
1350        state.write_usize(self.len());
1351        for level in self.segments() {
1352            level.as_str().hash(state);
1353        }
1354    }
1355}
1356
1357impl fmt::Debug for StorePath {
1358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1359        write!(f, "StorePath({})", self)
1360    }
1361}
1362
1363/// The joined spelling, written a level at a time rather than built and then
1364/// written: printing a path is not a reason to make one hold a second form of
1365/// itself.
1366impl fmt::Display for StorePath {
1367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1368        for (at, level) in self.segments().enumerate() {
1369            if at > 0 {
1370                f.write_char(SEPARATOR)?;
1371            }
1372
1373            for ch in level.as_str().chars() {
1374                if ch == SEPARATOR || ch == ESCAPE {
1375                    f.write_char(ESCAPE)?;
1376                }
1377                f.write_char(ch)?;
1378            }
1379        }
1380
1381        Ok(())
1382    }
1383}
1384
1385/// A path both of whose halves the compiler knows: the levels and the joined
1386/// form, checked against each other where they are written.
1387///
1388/// [`StorePath`] holds a run-time path behind an `Arc`, which costs two things
1389/// a declaration cannot pay. It has a destructor, so a `static` built from one
1390/// cannot use `..` to fill in the rest of a struct; and its joined form cannot
1391/// be read in a `const`, which is where the checks over a declaration run.
1392/// This type has neither: it is `Copy`, it owns nothing, and everything about
1393/// it is available while the code is compiled.
1394///
1395/// It is what a declaration carries. Turn it into a `StorePath` with
1396/// [`StaticPath::path`] wherever a run-time path is wanted.
1397#[derive(Clone, Copy, PartialEq, Eq)]
1398pub struct StaticPath {
1399    segments: &'static [&'static str],
1400    joined: &'static str,
1401}
1402
1403impl StaticPath {
1404    /// # Panics
1405    ///
1406    /// If any level is empty, or if `joined` is not the joined form of
1407    /// `segments` - at compile time, which is the only place this is meant to
1408    /// be called. See [`StorePath::from_static`], which checks the same pair.
1409    pub const fn new(segments: &'static [&'static str], joined: &'static str) -> Self {
1410        check_static(segments, joined);
1411
1412        Self { segments, joined }
1413    }
1414
1415    /// The path that is under nothing.
1416    pub const fn root() -> Self {
1417        Self {
1418            segments: &[],
1419            joined: "",
1420        }
1421    }
1422
1423    /// The whole path as one string, where a `const` can read it.
1424    pub const fn as_str(&self) -> &'static str {
1425        self.joined
1426    }
1427
1428    pub const fn is_root(&self) -> bool {
1429        self.segments.is_empty()
1430    }
1431
1432    /// The same path, as the type the store addresses by. Allocates nothing:
1433    /// both halves are already `'static`.
1434    pub fn path(&self) -> StorePath {
1435        StorePath::from_static(self.segments, self.joined)
1436    }
1437}
1438
1439impl From<StaticPath> for StorePath {
1440    fn from(at: StaticPath) -> Self {
1441        at.path()
1442    }
1443}
1444
1445impl PartialEq<str> for StaticPath {
1446    fn eq(&self, other: &str) -> bool {
1447        self.joined == other
1448    }
1449}
1450
1451impl AsRef<str> for StaticPath {
1452    fn as_ref(&self) -> &str {
1453        self.joined
1454    }
1455}
1456
1457impl fmt::Debug for StaticPath {
1458    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1459        write!(f, "StaticPath({:?})", self.joined)
1460    }
1461}
1462
1463impl fmt::Display for StaticPath {
1464    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1465        f.write_str(self.joined)
1466    }
1467}
1468
1469/// Written as the joined form, which is the only form a document holds.
1470impl serde::Serialize for StorePath {
1471    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1472        serializer.serialize_str(self.as_str())
1473    }
1474}
1475
1476/// Read back through [`StorePath::parse_joined`], so the invariants are checked
1477/// where the document is read rather than wherever somebody first asks for a
1478/// level: a key with a nameless level or a dangling escape is refused here and
1479/// cannot reach anything that would have to answer for it later.
1480impl<'de> serde::Deserialize<'de> for StorePath {
1481    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1482        let joined = <Cow<'de, str>>::deserialize(deserializer)?;
1483        StorePath::parse_joined(&joined).map_err(serde::de::Error::custom)
1484    }
1485}
1486
1487#[cfg(test)]
1488mod tests {
1489    use super::*;
1490    use proptest::prelude::*;
1491
1492    fn shares_a_key_by_levels(a: &StorePath, b: &StorePath) -> bool {
1493        let a: Vec<_> = a.segments().collect();
1494        let b: Vec<_> = b.segments().collect();
1495        let common = a.len().min(b.len());
1496        a[..common] == b[..common]
1497    }
1498
1499    #[test]
1500    fn two_subtrees_overlap_exactly_when_one_holds_the_other() {
1501        let cases: &[(&[&str], &[&str], bool, &str)] = &[
1502            (&["ui"], &["ui"], true, "the same path"),
1503            (&["ui"], &["ui", "theme"], true, "a child"),
1504            (
1505                &["ui"],
1506                &["ui", "a", "b"],
1507                true,
1508                "a grandchild - a subtree is not one level",
1509            ),
1510            (&["ui", "theme"], &["ui", "width"], false, "siblings"),
1511            (&["ui"], &["net"], false, "strangers"),
1512            (
1513                &["ui"],
1514                &["uix"],
1515                false,
1516                "a string prefix is not a level prefix",
1517            ),
1518            (
1519                &["ui"],
1520                &["uix", "width"],
1521                false,
1522                "and neither is its subtree",
1523            ),
1524            (
1525                &["ui"],
1526                &["ui!x"],
1527                false,
1528                "`!` sorts below the separator, and is still not under `ui`",
1529            ),
1530            (
1531                &["ui.theme"],
1532                &["ui"],
1533                false,
1534                "one level literally named `ui.theme`, escaped as `ui\\.theme`",
1535            ),
1536            (
1537                &["ui.theme"],
1538                &["ui", "theme"],
1539                false,
1540                "which is a different place from two levels",
1541            ),
1542            (&[], &["ui", "theme"], true, "the root holds everything"),
1543        ];
1544
1545        for (a, b, want, why) in cases {
1546            let (a, b) = (StorePath::from_segments(*a), StorePath::from_segments(*b));
1547            assert_eq!(a.overlaps(&b), *want, "{why}: {a} vs {b}");
1548            assert_eq!(
1549                b.overlaps(&a),
1550                *want,
1551                "{why}, the other way round: {b} vs {a}"
1552            );
1553        }
1554    }
1555
1556    use crate::strategies::{key as key_strategy, name_holding_the_separator, segment};
1557
1558    fn segment_strategy() -> impl Strategy<Value = String> {
1559        segment()
1560    }
1561
1562    fn dotted_segment() -> impl Strategy<Value = String> {
1563        name_holding_the_separator().prop_map(|(_, _, joined)| joined)
1564    }
1565
1566    fn path_strategy() -> impl Strategy<Value = Vec<String>> {
1567        prop::collection::vec(segment_strategy(), 1..5)
1568    }
1569
1570    fn hash_of(value: &impl Hash) -> u64 {
1571        let mut hasher = std::collections::hash_map::DefaultHasher::new();
1572        value.hash(&mut hasher);
1573        hasher.finish()
1574    }
1575
1576    proptest! {
1577        #[test]
1578        fn two_subtrees_meet_exactly_when_one_starts_the_other(
1579            a in path_strategy(),
1580            b in path_strategy(),
1581        ) {
1582            let (a, b) = (StorePath::from_segments(&a), StorePath::from_segments(&b));
1583
1584            prop_assert_eq!(
1585                a.overlaps(&b),
1586                shares_a_key_by_levels(&a, &b),
1587                "the joined form and the levels disagree: {} vs {}", a, b
1588            );
1589            prop_assert_eq!(a.overlaps(&b), b.overlaps(&a), "{} vs {}", a, b);
1590
1591            // Nested means the deeper one is itself the shared key.
1592            if a.overlaps(&b) {
1593                let deeper = if a.len() >= b.len() { &a } else { &b };
1594                prop_assert!(deeper.starts_with(&a));
1595                prop_assert!(deeper.starts_with(&b));
1596            }
1597        }
1598
1599        #[test]
1600        fn a_path_meets_everything_grown_from_it(
1601            head in path_strategy(),
1602            tail in prop::collection::vec(segment_strategy(), 0..4),
1603        ) {
1604            let a = StorePath::from_segments(&head);
1605            let b = StorePath::from_segments(head.iter().chain(&tail));
1606
1607            prop_assert!(a.overlaps(&b), "{} does not hold {}", a, b);
1608            prop_assert!(b.starts_with(&a), "{} vs {}", a, b);
1609        }
1610
1611        #[test]
1612        fn two_branches_of_one_level_never_meet(
1613            head in path_strategy(),
1614            left in segment_strategy(),
1615            right in segment_strategy(),
1616            left_tail in prop::collection::vec(segment_strategy(), 0..3),
1617            right_tail in prop::collection::vec(segment_strategy(), 0..3),
1618        ) {
1619            prop_assume!(left != right);
1620
1621            let a = StorePath::from_segments(head.iter().chain([&left]).chain(&left_tail));
1622            let b = StorePath::from_segments(head.iter().chain([&right]).chain(&right_tail));
1623
1624            prop_assert!(!a.overlaps(&b), "siblings met: {} vs {}", a, b);
1625        }
1626
1627        #[test]
1628        fn a_separator_inside_a_name_is_never_a_level(
1629            segments in prop::collection::vec(dotted_segment(), 1..16)
1630        ) {
1631            let path = StorePath::from_segments(&segments);
1632
1633            prop_assert_eq!(path.len(), segments.len());
1634            prop_assert_eq!(
1635                path.segments().map(|s| s.to_string()).collect::<Vec<_>>(),
1636                segments.clone()
1637            );
1638
1639            let split: Vec<&str> = segments.iter().flat_map(|s| s.split('.')).collect();
1640            if let Ok(taken_apart) = StorePath::try_from_segments(&split) {
1641                prop_assert_ne!(taken_apart, path);
1642            }
1643        }
1644        #[test]
1645        fn the_joined_form_round_trips(segments in path_strategy()) {
1646            let path = StorePath::from_segments(&segments);
1647            prop_assert_eq!(StorePath::parse_joined(path.as_str()).unwrap(), path);
1648        }
1649
1650        #[test]
1651        fn different_levels_never_join_to_one_key(a in path_strategy(), b in path_strategy()) {
1652            let pa = StorePath::from_segments(&a);
1653            let pb = StorePath::from_segments(&b);
1654
1655            prop_assert_eq!(a == b, pa.as_str() == pb.as_str());
1656        }
1657
1658        #[test]
1659        fn a_prefix_is_stripped_back_off(head in path_strategy(), tail in path_strategy()) {
1660            let prefix = StorePath::from_segments(&head);
1661            let full = prefix.join(&StorePath::from_segments(&tail));
1662
1663            prop_assert!(full.starts_with(&prefix));
1664            prop_assert_eq!(
1665                full.strip_prefix(&prefix).unwrap(),
1666                StorePath::from_segments(&tail)
1667            );
1668        }
1669
1670        #[test]
1671        fn a_path_does_not_remember_how_it_was_built(segments in path_strategy()) {
1672            let all_at_once = StorePath::from_segments(&segments);
1673
1674            let mut one_at_a_time = StorePath::root();
1675            for segment in &segments {
1676                one_at_a_time = one_at_a_time.push(segment);
1677            }
1678
1679            let by_joining = segments.iter().fold(StorePath::root(), |acc, segment| {
1680                acc.join(&StorePath::segment(segment))
1681            });
1682
1683            prop_assert_eq!(&one_at_a_time, &all_at_once);
1684            prop_assert_eq!(&by_joining, &all_at_once);
1685            prop_assert_eq!(one_at_a_time.as_str(), all_at_once.as_str());
1686        }
1687
1688        #[test]
1689        fn any_spelling_of_the_levels_gives_the_same_path(segments in path_strategy()) {
1690            let built = StorePath::from_segments(&segments);
1691            let refs: Vec<&str> = segments.iter().map(String::as_str).collect();
1692
1693            prop_assert_eq!(refs.as_slice().into_store_path().unwrap(), built.clone());
1694            prop_assert_eq!(segments.clone().into_store_path().unwrap(), built.clone());
1695            prop_assert_eq!((&built).into_store_path().unwrap(), built.clone());
1696            prop_assert_eq!(built.clone().into_store_path().unwrap(), built);
1697        }
1698
1699        #[test]
1700        fn every_path_is_under_the_root(segments in path_strategy()) {
1701            let path = StorePath::from_segments(&segments);
1702            let root = StorePath::root();
1703
1704            prop_assert!(path.starts_with(&root));
1705            prop_assert_eq!(path.strip_prefix(&root).unwrap(), path.clone());
1706            prop_assert!(!root.starts_with(&path));
1707        }
1708
1709        #[test]
1710        fn a_level_with_no_name_is_a_level_like_any_other(
1711            segments in path_strategy(),
1712            at in 0usize..8
1713        ) {
1714            let mut with_a_hole = segments.clone();
1715            let at = at % (with_a_hole.len() + 1);
1716            with_a_hole.insert(at, String::new());
1717
1718            let path = StorePath::try_from_segments(&with_a_hole).unwrap();
1719            let held = path.segment_at(at).unwrap();
1720
1721            prop_assert_eq!(path.len(), with_a_hole.len());
1722            prop_assert_eq!(held.as_str(), "");
1723
1724            let pushed = StorePath::from_segments(&segments).push("");
1725            let last = pushed.segment_at(segments.len()).unwrap();
1726
1727            prop_assert_eq!(pushed.len(), segments.len() + 1);
1728            prop_assert_eq!(last.as_str(), "");
1729        }
1730
1731        #[test]
1732        fn every_separator_inside_a_name_is_escaped(segments in path_strategy()) {
1733            let path = StorePath::from_segments(&segments);
1734
1735            let mut unescaped = 0usize;
1736            let mut escaped = false;
1737            for ch in path.as_str().chars() {
1738                match ch {
1739                    _ if escaped => escaped = false,
1740                    ESCAPE => escaped = true,
1741                    SEPARATOR => unescaped += 1,
1742                    _ => {}
1743                }
1744            }
1745
1746            prop_assert_eq!(unescaped, segments.len() - 1);
1747        }
1748
1749        #[test]
1750        fn growing_a_name_never_makes_it_a_prefix(
1751            head in path_strategy(),
1752            extra in segment_strategy()
1753        ) {
1754            let base = StorePath::from_segments(&head);
1755
1756            let mut grown = head.clone();
1757            grown.last_mut().unwrap().push_str(&extra);
1758            let longer = StorePath::from_segments(&grown);
1759
1760            prop_assert!(longer.as_str().starts_with(base.as_str()));
1761
1762            prop_assert_ne!(&longer, &base);
1763            prop_assert!(!longer.starts_with(&base));
1764            prop_assert!(!base.starts_with(&longer));
1765        }
1766
1767        #[test]
1768        fn stripping_succeeds_exactly_when_the_prefix_matches(
1769            a in path_strategy(),
1770            b in path_strategy()
1771        ) {
1772            let path = StorePath::from_segments(&a);
1773            let candidate = StorePath::from_segments(&b);
1774
1775            prop_assert_eq!(
1776                path.strip_prefix(&candidate).is_some(),
1777                path.starts_with(&candidate)
1778            );
1779        }
1780
1781        #[test]
1782        fn a_key_that_parses_joins_back_to_itself(key in key_strategy()) {
1783            if let Ok(path) = StorePath::parse_joined(&key) {
1784                prop_assert_eq!(path.as_str(), key.as_str());
1785            }
1786        }
1787
1788        #[test]
1789        fn a_key_with_a_nameless_level_reads_that_level_as_nameless(
1790            head in path_strategy(),
1791            tail in path_strategy(),
1792            at in 0usize..3
1793        ) {
1794            let head = StorePath::from_segments(&head);
1795            let tail = StorePath::from_segments(&tail);
1796
1797            let (key, hole, levels) = match at % 3 {
1798                0 => (format!("{SEPARATOR}{tail}"), 0, 1 + tail.len()),
1799                1 => (format!("{head}{SEPARATOR}"), head.len(), head.len() + 1),
1800                _ => (
1801                    format!("{head}{SEPARATOR}{SEPARATOR}{tail}"),
1802                    head.len(),
1803                    head.len() + 1 + tail.len(),
1804                ),
1805            };
1806
1807            let read = StorePath::parse_joined(&key).unwrap();
1808            let empty = read.segment_at(hole).unwrap();
1809
1810            prop_assert_eq!(read.len(), levels, "key: {:?}", key);
1811            prop_assert_eq!(empty.as_str(), "", "key: {:?}", key);
1812        }
1813
1814
1815        #[test]
1816        fn a_borrowed_path_answers_like_the_owned_one(
1817            key in path_strategy(),
1818            prefix in path_strategy()
1819        ) {
1820            let owned = StorePath::from_segments(&key);
1821            let under = StorePath::from_segments(&prefix);
1822            let borrowed = PathRef::from(&owned);
1823
1824            prop_assert_eq!(borrowed.as_str(), owned.as_str());
1825            prop_assert_eq!(borrowed.is_root(), owned.is_root());
1826            prop_assert_eq!(borrowed.level_under(&under), owned.level_under(&under));
1827            prop_assert_eq!(borrowed.name_under(&under), owned.name_under(&under));
1828            prop_assert_eq!(borrowed.to_path(), owned);
1829        }
1830
1831        #[test]
1832        fn a_key_borrows_exactly_when_it_parses(key in key_strategy()) {
1833            prop_assert_eq!(
1834                PathRef::parse(&key).is_ok(),
1835                StorePath::parse_joined(&key).is_ok(),
1836                "key: {:?}", key
1837            );
1838
1839            if let Ok(borrowed) = PathRef::parse(&key) {
1840                let owned = borrowed.to_path();
1841
1842                prop_assert_eq!(borrowed.as_str(), key.as_str());
1843                prop_assert_eq!(owned.as_str(), key.as_str());
1844            }
1845        }
1846
1847        #[test]
1848        fn the_range_a_key_gives_holds_exactly_the_subtree(
1849            head in path_strategy(),
1850            other in path_strategy()
1851        ) {
1852            let prefix = StorePath::from_segments(&head);
1853            let key = StorePath::from_segments(&other);
1854
1855            let under = prefix.key();
1856            let (low, high) = under.subtree();
1857            let bytes = key.key();
1858            let bytes = bytes.as_bytes();
1859
1860            let in_range = bytes >= low && high.as_ref().is_none_or(|h| bytes < h.as_slice());
1861
1862            prop_assert_eq!(
1863                in_range,
1864                key.starts_with(&prefix),
1865                "{} against the subtree of {}", key, prefix
1866            );
1867        }
1868
1869        #[test]
1870        fn everything_grown_from_a_path_is_in_its_range(
1871            head in path_strategy(),
1872            tail in prop::collection::vec(segment_strategy(), 0..4)
1873        ) {
1874            let prefix = StorePath::from_segments(&head);
1875            let under = prefix.join(&StorePath::from_segments(&tail));
1876
1877            prop_assert!(
1878                under.key().under(&prefix.key()),
1879                "{} is under {} and the range left it out", under, prefix
1880            );
1881        }
1882
1883        #[test]
1884        fn a_key_is_under_a_path_exactly_when_it_starts_with_it_and_a_separator(
1885            head in path_strategy(),
1886            tail in path_strategy(),
1887            other in path_strategy()
1888        ) {
1889            let prefix = StorePath::from_segments(&head);
1890            let boundary = format!("{}{}", prefix.as_str(), SEPARATOR);
1891
1892            let candidates = [
1893                prefix.join(&StorePath::from_segments(&tail)),
1894                StorePath::from_segments(&other),
1895                prefix.clone(),
1896                StorePath::from_segments(&tail).join(&prefix),
1897            ];
1898
1899            for candidate in candidates {
1900                let under = candidate.starts_with(&prefix) && candidate != prefix;
1901                prop_assert_eq!(under, candidate.as_str().starts_with(&boundary));
1902            }
1903        }
1904
1905        #[test]
1906        fn no_call_makes_a_path_from_segments_would_refuse(
1907            a in path_strategy(),
1908            b in path_strategy()
1909        ) {
1910            let pa = StorePath::from_segments(&a);
1911            let pb = StorePath::from_segments(&b);
1912
1913            let mut derived = vec![
1914                pa.join(&pb),
1915                pa.push("x"),
1916                StorePath::parse_joined(pa.as_str()).unwrap(),
1917                pa.join(&StorePath::root()),
1918            ];
1919            derived.extend(pa.parent());
1920            derived.extend(pa.join(&pb).strip_prefix(&pa));
1921
1922            for path in derived {
1923                let rebuilt = match StorePath::try_from_segments(path.segments()) {
1924                    Ok(rebuilt) => rebuilt,
1925                    Err(StorePathError::EmptyPath) => {
1926                        prop_assert!(path.is_root());
1927                        continue;
1928                    }
1929                    Err(other) => {
1930                        return Err(TestCaseError::fail(format!(
1931                            "a derived path holds an empty level: {other}"
1932                        )));
1933                    }
1934                };
1935                prop_assert_eq!(&rebuilt, &path);
1936                prop_assert_eq!(rebuilt.as_str(), path.as_str());
1937            }
1938        }
1939
1940        #[test]
1941        fn comparing_names_answers_what_comparing_their_keys_answers(
1942            a in segment_strategy(),
1943            b in segment_strategy()
1944        ) {
1945            let under = StorePath::segment("m");
1946            let ka = under.push(&a);
1947            let kb = under.push(&b);
1948
1949            prop_assert_eq!(
1950                a.cmp(&b),
1951                ka.key().as_bytes().cmp(kb.key().as_bytes()),
1952                "names {:?} and {:?} became keys {:?} and {:?}",
1953                a, b, ka, kb
1954            );
1955        }
1956
1957        #[test]
1958        fn equality_hashing_and_the_key_agree(a in path_strategy(), b in path_strategy()) {
1959            let pa = StorePath::from_segments(&a);
1960            let pb = StorePath::from_segments(&b);
1961
1962            let left = [pa.clone(), pa.parent().unwrap_or_else(StorePath::root), StorePath::root()];
1963            let right = [pb.clone(), pa.join(&pb).strip_prefix(&pa).unwrap(), StorePath::root()];
1964
1965            for x in &left {
1966                for y in &right {
1967                    prop_assert_eq!(x == y, x.as_str() == y.as_str());
1968                    if x == y {
1969                        prop_assert_eq!(hash_of(x), hash_of(y));
1970                    }
1971                }
1972            }
1973        }
1974
1975        #[test]
1976        fn the_const_check_accepts_what_the_join_writes(segments in path_strategy()) {
1977            let path = StorePath::from_segments(&segments);
1978            let levels: Vec<&str> = segments.iter().map(String::as_str).collect();
1979
1980            prop_assert!(
1981                joins_to(&levels, path.as_str()),
1982                "refused its own join: {:?}", path.as_str()
1983            );
1984
1985            let joined = path.as_str();
1986            let perturbed = [
1987                format!("{joined}{SEPARATOR}"),
1988                format!("{SEPARATOR}{joined}"),
1989                format!("{joined}{ESCAPE}"),
1990                format!("{joined}x"),
1991            ];
1992
1993            for candidate in perturbed {
1994                prop_assert!(
1995                    !joins_to(&levels, &candidate),
1996                    "accepted {:?} as the join of {:?}", candidate, levels
1997                );
1998            }
1999
2000            if levels.len() > 1 {
2001                let shorter = StorePath::from_segments(&segments[..segments.len() - 1]);
2002                prop_assert!(!joins_to(&levels, shorter.as_str()));
2003            }
2004        }
2005    }
2006
2007    #[test]
2008    fn the_encoding_is_a_backslash_before_the_separator() {
2009        assert_eq!(StorePath::segment("dark.mode").as_str(), "dark\\.mode");
2010        assert_eq!(
2011            StorePath::from_segments(["dark", "mode"]).as_str(),
2012            "dark.mode"
2013        );
2014    }
2015
2016    #[test]
2017    fn a_key_that_is_not_a_path_is_refused_at_both_doors() {
2018        let refused = [
2019            ("a\\", StorePathError::DanglingEscape),
2020            ("a\\x", StorePathError::DanglingEscape),
2021        ];
2022
2023        for (key, why) in refused {
2024            assert_eq!(
2025                StorePath::parse_joined(key).unwrap_err(),
2026                why,
2027                "parse_joined took {key:?}"
2028            );
2029            assert!(
2030                PathRef::parse(key).is_err(),
2031                "PathRef::parse took {key:?}, and the two doors must agree"
2032            );
2033        }
2034    }
2035
2036    #[test]
2037    fn a_subtree_is_one_run_with_nothing_of_anyone_elses_in_it() {
2038        let pot = StorePath::segment("pot");
2039        let luck = StorePath::segment("pot!luck");
2040        let ato = StorePath::from_segments(["pot", "ato"]);
2041        let potato = StorePath::segment("potato");
2042
2043        let mut sorted = [&potato, &luck, &ato, &pot];
2044        sorted.sort();
2045
2046        assert_eq!(
2047            sorted,
2048            [&pot, &ato, &luck, &potato],
2049            "everything under `pot` has to come directly after it"
2050        );
2051
2052        assert!(ato.starts_with(&pot));
2053        assert!(!luck.starts_with(&pot));
2054        assert!(!potato.starts_with(&pot));
2055    }
2056
2057    #[test]
2058    fn a_shared_prefix_answers_as_the_path_it_spells() {
2059        let full = StorePath::from_segments(["ui", "panels", "left", "width"]);
2060
2061        for (up, spelled) in [(1, "ui.panels.left"), (2, "ui.panels"), (3, "ui"), (4, "")] {
2062            let mut at = full.clone();
2063            for _ in 0..up {
2064                at = at.parent().expect("there is a level to drop");
2065            }
2066
2067            let built = StorePath::parse_joined(spelled).expect("the spelling parses");
2068
2069            assert_eq!(at.as_str(), spelled);
2070            assert_eq!(at, built);
2071            assert_eq!(at.len(), built.len());
2072            assert_eq!(at.is_root(), built.is_root());
2073            assert_eq!(at.key().as_bytes(), built.key().as_bytes());
2074            assert_eq!(
2075                at.segments().collect::<Vec<_>>(),
2076                built.segments().collect::<Vec<_>>()
2077            );
2078        }
2079    }
2080
2081    #[test]
2082    fn a_prefix_of_a_name_holding_the_separator_cuts_where_the_level_does() {
2083        let full = StorePath::from_segments(["dark.mode", "width"]);
2084        let up = full.parent().expect("there is a level to drop");
2085
2086        assert_eq!(up.len(), 1);
2087        assert_eq!(up.name().expect("one level").as_str(), "dark.mode");
2088        assert_eq!(up.as_str(), "dark\\.mode");
2089        assert_eq!(up, StorePath::segment("dark.mode"));
2090    }
2091
2092    #[test]
2093    fn walking_upwards_ends_at_the_root_and_holds_the_path_itself() {
2094        let full = StorePath::from_segments(["a", "b", "c"]);
2095
2096        let seen: Vec<String> = full.upwards().map(|at| at.as_str().to_string()).collect();
2097
2098        assert_eq!(seen, ["a.b.c", "a.b", "a", ""]);
2099        assert_eq!(StorePath::root().upwards().count(), 1);
2100    }
2101
2102    #[test]
2103    fn the_root_is_empty_and_stays_empty() {
2104        let root = StorePath::root();
2105
2106        assert!(root.is_root());
2107        assert_eq!(root.as_str(), "");
2108        assert_eq!(StorePath::parse_joined("").unwrap(), root);
2109        assert_eq!(root.parent(), None);
2110        assert_eq!(root.name(), None);
2111    }
2112
2113    #[test]
2114    fn a_key_no_join_could_have_written_is_not_a_second_name_for_one() {
2115        assert_ne!(
2116            StorePath::parse_joined("a\\b").ok(),
2117            StorePath::parse_joined("ab").ok()
2118        );
2119        assert_ne!(
2120            StorePath::parse_joined("a\\").ok(),
2121            StorePath::parse_joined("a").ok()
2122        );
2123    }
2124
2125    #[test]
2126    fn the_root_has_no_separator_boundary() {
2127        let root = StorePath::root();
2128        let child = StorePath::from_segments(["ui", "width"]);
2129
2130        assert!(child.starts_with(&root));
2131        assert_eq!(format!("{}{}", root.as_str(), SEPARATOR), ".");
2132        assert!(!child.as_str().starts_with(SEPARATOR));
2133        assert!(
2134            !StorePath::segment(".hidden")
2135                .as_str()
2136                .starts_with(SEPARATOR)
2137        );
2138    }
2139
2140    #[test]
2141    fn a_subtree_boundary_does_not_cover_the_node_itself() {
2142        let node = StorePath::segment("ui");
2143        let boundary = format!("{}{}", node.as_str(), SEPARATOR);
2144
2145        assert!(node.push("width").as_str().starts_with(&boundary));
2146        assert!(!node.as_str().starts_with(&boundary));
2147        assert!(node.starts_with(&node));
2148    }
2149
2150    #[test]
2151    fn a_key_carries_what_a_glob_pattern_reads() {
2152        let path = StorePath::segment("a*b[c]?\u{0}d");
2153
2154        assert_eq!(path.as_str(), "a*b[c]?\u{0}d");
2155    }
2156
2157    #[test]
2158    fn a_path_hashes_by_its_levels_however_it_arrived() {
2159        let built = StorePath::from_segments(["ui", "dark.mode"]);
2160        let parsed = StorePath::parse_joined("ui.dark\\.mode").expect("the spelling parses");
2161
2162        assert_eq!(built, parsed);
2163        assert_eq!(hash_of(&built), hash_of(&parsed));
2164    }
2165
2166    #[test]
2167    fn two_levels_do_not_hash_as_the_one_they_spell() {
2168        let two = StorePath::from_segments(["a", "b"]);
2169        let one = StorePath::segment("ab");
2170
2171        assert_ne!(two, one);
2172        assert_ne!(hash_of(&two), hash_of(&one));
2173    }
2174
2175    #[test]
2176    fn a_path_flattened_to_one_level_is_named_by_what_it_spells() {
2177        let flat = StorePath::from_segments(["ui", "theme"]).as_one_level();
2178
2179        assert_eq!(flat.len(), 1);
2180        assert_eq!(flat.name().expect("one level has a name"), "ui.theme");
2181    }
2182
2183    #[test]
2184    fn a_level_holding_a_separator_flattens_apart_from_the_two_it_reads_as() {
2185        let one = StorePath::segment("ui.theme").as_one_level();
2186        let two = StorePath::from_segments(["ui", "theme"]).as_one_level();
2187
2188        assert_ne!(one, two);
2189        assert_eq!(
2190            StorePath::parse_joined(one.name().expect("one level has a name").as_str())
2191                .expect("the spelling parses"),
2192            StorePath::segment("ui.theme")
2193        );
2194    }
2195
2196    #[test]
2197    fn two_spellings_of_one_name_are_two_paths() {
2198        let precomposed = StorePath::segment("caf\u{e9}");
2199        let decomposed = StorePath::segment("cafe\u{301}");
2200
2201        assert_ne!(precomposed, decomposed);
2202        assert_ne!(precomposed.as_str(), decomposed.as_str());
2203        assert_eq!(precomposed.to_string().chars().count(), 4);
2204        assert_eq!(decomposed.to_string().chars().count(), 5);
2205    }
2206
2207    #[test]
2208    fn walking_a_document_borrows_each_level() {
2209        let path = StorePath::from_segments(["ui", "window", "width"]);
2210
2211        assert_eq!(
2212            path.segments()
2213                .map(|l| l.as_str().to_string())
2214                .collect::<Vec<_>>(),
2215            ["ui", "window", "width"]
2216        );
2217        assert_eq!(
2218            path.segment_at(1).as_ref().map(Level::as_str),
2219            Some("window")
2220        );
2221        assert_eq!(path.segment_at(3), None);
2222    }
2223
2224    #[test]
2225    fn a_static_path_is_the_same_path() {
2226        static UI_WIDTH: StorePath = StorePath::from_static(&["ui", "width"], "ui.width");
2227
2228        assert_eq!(UI_WIDTH, StorePath::from_segments(["ui", "width"]));
2229        assert_eq!(UI_WIDTH.as_str(), "ui.width");
2230        assert_eq!(UI_WIDTH.name().as_ref().map(Level::as_str), Some("width"));
2231        assert!(UI_WIDTH.starts_with(&StorePath::from_static(&["ui"], "ui")));
2232    }
2233
2234    #[test]
2235    fn a_static_path_carries_what_a_name_holds() {
2236        static ODD: StorePath =
2237            StorePath::from_static(&["dark.mode", "a\\b"], "dark\\.mode.a\\\\b");
2238        static NOTHING: StorePath = StorePath::from_static(&[], "");
2239
2240        assert_eq!(ODD, StorePath::from_segments(["dark.mode", "a\\b"]));
2241        assert_eq!(
2242            ODD.as_str(),
2243            StorePath::from_segments(["dark.mode", "a\\b"]).as_str()
2244        );
2245        assert_eq!(NOTHING, StorePath::root());
2246    }
2247}