Skip to main content

firewood_storage/path/
mod.rs

1// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
2// See the file LICENSE.md for licensing terms.
3
4mod buf;
5mod component;
6mod joined;
7mod packed;
8mod split;
9
10pub use self::buf::{PartialPath, PathBuf, PathGuard};
11pub use self::component::{ComponentIter, PathComponent, PathComponentSliceExt};
12pub use self::joined::JoinedPath;
13pub use self::packed::{PackedBytes, PackedPathComponents, PackedPathRef};
14pub use self::split::{IntoSplitPath, PathCommonPrefix, SplitPath};
15
16/// A trie path of components with different underlying representations.
17///
18/// The underlying representation does not need to be a contiguous array of
19/// [`PathComponent`], but it must be possible to iterate over them in order
20/// as well as have a known length.
21pub trait TriePath {
22    /// The iterator returned by [`TriePath::components`].
23    type Components<'a>: Iterator<Item = PathComponent> + Clone + 'a
24    where
25        Self: 'a;
26
27    /// The length, in path components, of this path.
28    fn len(&self) -> usize;
29
30    /// Returns true if this path is empty (i.e. has length 0).
31    fn is_empty(&self) -> bool {
32        self.len() == 0
33    }
34
35    /// Returns an iterator over the components of this path.
36    fn components(&self) -> Self::Components<'_>;
37
38    /// Returns a contiguous view of this path's components.
39    ///
40    /// If the underlying representation is already contiguous, this should be a
41    /// cheap operation (i.e. no allocations or copies). If not, this may allocate
42    /// and copy the components into a contiguous buffer.
43    fn as_component_slice(&self) -> PartialPath<'_>;
44
45    /// Appends the provided path segment to this path, returning a new joined
46    /// path that represents the concatenation of the two paths.
47    ///
48    /// The returned path is a view over the two input paths and does not
49    /// allocate. The input paths are consumed and ownership is taken.
50    fn append<S>(self, suffix: S) -> JoinedPath<Self, S>
51    where
52        Self: Sized,
53        S: TriePath,
54    {
55        JoinedPath::new(self, suffix)
56    }
57
58    /// Prepends the provided path segment to this path, returning a new joined
59    /// path that represents the concatenation of the two paths.
60    ///
61    /// The inverse of [`TriePath::append`].
62    ///
63    /// The returned path is a view over the two input paths and does not
64    /// allocate. The input paths are consumed and ownership is taken.
65    fn prepend<P>(self, prefix: P) -> JoinedPath<P, Self>
66    where
67        Self: Sized,
68        P: TriePath,
69    {
70        prefix.append(self)
71    }
72
73    /// Compares this path against another path for equality using path component
74    /// equality.
75    ///
76    /// This is analogous to [`Iterator::eq`] and is different than [`PartialEq`]
77    /// which may have different semantics depending on the underlying type and
78    /// representation as well as may not be implemented for the cross-type
79    /// comparisons.
80    fn path_eq<T: TriePath + ?Sized>(&self, other: &T) -> bool {
81        self.len() == other.len() && self.components().eq(other.components())
82    }
83
84    /// Compares this path against another path using path-component lexicographic
85    /// ordering. Strict prefixes are less than their longer counterparts.
86    ///
87    /// This is analogous to [`Iterator::cmp`] and is different than [`Ord`]
88    /// which may have different semantics depending on the underlying type and
89    /// representation as well as may not be implemented for the cross-type
90    /// comparisons.
91    fn path_cmp<T: TriePath + ?Sized>(&self, other: &T) -> std::cmp::Ordering {
92        self.components().cmp(other.components())
93    }
94
95    /// Returns a wrapper type that implements [`std::fmt::Display`] and
96    /// [`std::fmt::Debug`] for this path.
97    fn display(&self) -> DisplayPath<'_, Self> {
98        DisplayPath { path: self }
99    }
100}
101
102/// Constructor for a trie path from a set of unpacked bytes; where each byte
103/// is a whole path component regardless of the normal width of a path component.
104///
105/// For 256-ary tries, this is the bytes as-is.
106///
107/// For hexary tries, each byte must occupy only the lower 4 bits. Any byte with
108/// a bit set in the upper 4 bits will result in an error.
109pub trait TriePathFromUnpackedBytes<'input>: TriePath + Sized {
110    /// The error type returned if the bytes are invalid.
111    type Error;
112
113    /// Constructs a path from the given unpacked bytes.
114    ///
115    /// For hexary tries, each byte must be in the range 0x00 to 0x0F inclusive.
116    /// Any byte outside this range will result in an error.
117    ///
118    /// # Errors
119    ///
120    /// - The input is invalid.
121    fn path_from_unpacked_bytes(bytes: &'input [u8]) -> Result<Self, Self::Error>;
122}
123
124/// Constructor for a trie path from a set of packed bytes; where each byte contains
125/// as many path components as possible.
126///
127/// For 256-ary tries, this is just the bytes as-is.
128///
129/// For hexary tries, each byte contains two path components; one in the upper 4
130/// bits and one in the lower 4 bits, in big-endian order. The resulting path
131/// will always have an even length (`bytes.len() * 2`).
132///
133/// For future compatibility, this trait only supports paths where the width of
134/// a path component is a factor of 8 (i.e. 1, 2, 4, or 8 bits).
135pub trait TriePathFromPackedBytes<'input>: Sized {
136    /// Constructs a path from the given packed bytes.
137    fn path_from_packed_bytes(bytes: &'input [u8]) -> Self;
138}
139
140/// Converts this path to an iterator over its packed bytes.
141pub trait TriePathAsPackedBytes {
142    /// The iterator type returned by [`TriePathAsPackedBytes::as_packed_bytes`].
143    type PackedBytesIter<'a>: Iterator<Item = u8>
144    where
145        Self: 'a;
146
147    /// Returns an iterator over the packed bytes of this path.
148    ///
149    /// If the final path component does not fill a whole byte, it is padded with zero.
150    fn as_packed_bytes(&self) -> Self::PackedBytesIter<'_>;
151}
152
153#[inline]
154fn display_path(
155    f: &mut std::fmt::Formatter<'_>,
156    mut comp: impl Iterator<Item = PathComponent>,
157) -> std::fmt::Result {
158    comp.try_for_each(|c| write!(f, "{c}"))
159}
160
161/// A wrapper type that implements [`Display`](std::fmt::Display) and
162/// [`Debug`](std::fmt::Debug) for any type that implements [`TriePath`].
163pub struct DisplayPath<'a, P: TriePath + ?Sized> {
164    path: &'a P,
165}
166
167impl<P: TriePath + ?Sized> std::fmt::Debug for DisplayPath<'_, P> {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        display_path(f, self.path.components())
170    }
171}
172
173impl<P: TriePath + ?Sized> std::fmt::Display for DisplayPath<'_, P> {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        display_path(f, self.path.components())
176    }
177}
178
179impl<T: TriePath + ?Sized> TriePath for &T {
180    type Components<'a>
181        = T::Components<'a>
182    where
183        Self: 'a;
184
185    fn len(&self) -> usize {
186        (**self).len()
187    }
188
189    fn components(&self) -> Self::Components<'_> {
190        (**self).components()
191    }
192
193    fn as_component_slice(&self) -> PartialPath<'_> {
194        (**self).as_component_slice()
195    }
196}
197
198impl<T: TriePath + ?Sized> TriePath for &mut T {
199    type Components<'a>
200        = T::Components<'a>
201    where
202        Self: 'a;
203
204    fn len(&self) -> usize {
205        (**self).len()
206    }
207
208    fn components(&self) -> Self::Components<'_> {
209        (**self).components()
210    }
211
212    fn as_component_slice(&self) -> PartialPath<'_> {
213        (**self).as_component_slice()
214    }
215}
216
217impl<T: TriePath + ?Sized> TriePath for Box<T> {
218    type Components<'a>
219        = T::Components<'a>
220    where
221        Self: 'a;
222
223    fn len(&self) -> usize {
224        (**self).len()
225    }
226
227    fn components(&self) -> Self::Components<'_> {
228        (**self).components()
229    }
230
231    fn as_component_slice(&self) -> PartialPath<'_> {
232        (**self).as_component_slice()
233    }
234}
235
236impl<T: TriePath + ?Sized> TriePath for std::rc::Rc<T> {
237    type Components<'a>
238        = T::Components<'a>
239    where
240        Self: 'a;
241
242    fn len(&self) -> usize {
243        (**self).len()
244    }
245
246    fn components(&self) -> Self::Components<'_> {
247        (**self).components()
248    }
249
250    fn as_component_slice(&self) -> PartialPath<'_> {
251        (**self).as_component_slice()
252    }
253}
254
255impl<T: TriePath + ?Sized> TriePath for std::sync::Arc<T> {
256    type Components<'a>
257        = T::Components<'a>
258    where
259        Self: 'a;
260
261    fn len(&self) -> usize {
262        (**self).len()
263    }
264
265    fn components(&self) -> Self::Components<'_> {
266        (**self).components()
267    }
268
269    fn as_component_slice(&self) -> PartialPath<'_> {
270        (**self).as_component_slice()
271    }
272}