···7777pub mod disk;
7878pub mod mem;
7979pub mod mst;
8080+pub mod slice;
8081pub mod walk;
81828283pub use disk::{DiskBuilder, DiskDriver, DiskError, DiskStore, DriveError};
8384pub use mem::{DriverBuilder, LoadError, MemCar, PartialCar};
8485pub use mst::Commit;
8686+pub use slice::{SliceError, SliceProof, SliceWalker};
8587pub use walk::{MstError, Output, WalkError, WalkItem, noop};
86888789pub type Bytes = Vec<u8>;
+234
src/slice.rs
···11+//! Proven-range walking of CAR slices
22+33+use crate::{
44+ RepoPath,
55+ mem::MemCar,
66+ walk::{Output, WalkError, WalkItem},
77+};
88+use cid::Cid;
99+use std::ops::{Bound, RangeBounds};
1010+1111+/// Errors from [`MemCar::walk_slice`]
1212+#[derive(Debug, thiserror::Error)]
1313+pub enum SliceError {
1414+ #[error("walk error: {0}")]
1515+ Walk(#[from] WalkError),
1616+ /// A record within the requested range has no block in the CAR
1717+ #[error("record block absent within range: key={key:?} cid={cid}")]
1818+ IncompleteRange { key: RepoPath, cid: Cid },
1919+ /// An MST node block is absent within the requested range
2020+ #[error("MST node block absent within range: cid={cid}")]
2121+ MissingNode { cid: Cid },
2222+ /// Proof failed: preceding key does not bound the lower end of the range
2323+ #[error("preceding key {preceding:?} violates lower bound")]
2424+ BadPrecedingKey { preceding: RepoPath },
2525+ /// Proof failed: following key does not bound the upper end of the range
2626+ #[error("following key {following:?} violates upper bound")]
2727+ BadFollowingKey { following: RepoPath },
2828+}
2929+3030+/// Proof that the walked range is complete.
3131+///
3232+/// Returned by [`SliceWalker::finish`].
3333+pub struct SliceProof {
3434+ /// Key immediately before the lower bound in the full tree,
3535+ /// or `None` if the range starts at the tree's leftmost record.
3636+ pub preceding_key: Option<RepoPath>,
3737+ /// Key immediately after the upper bound in the full tree,
3838+ /// or `None` if the range ends at the tree's rightmost record.
3939+ pub following_key: Option<RepoPath>,
4040+}
4141+4242+enum SliceState {
4343+ Before,
4444+ In,
4545+ Done,
4646+}
4747+4848+/// Iterator-like walker over a proven range of the MST.
4949+///
5050+/// Created by [`MemCar::walk_slice`]. Call [`SliceWalker::next`] to yield
5151+/// records, then [`SliceWalker::finish`] to validate the proof.
5252+pub struct SliceWalker<'a> {
5353+ mem_car: &'a mut MemCar,
5454+ lower: Bound<String>,
5555+ upper: Bound<String>,
5656+ preceding_key: Option<RepoPath>,
5757+ following_key: Option<RepoPath>,
5858+ state: SliceState,
5959+}
6060+6161+impl SliceWalker<'_> {
6262+ /// Yield the next in-range record.
6363+ ///
6464+ /// Transparently skips boundary items outside the range. Returns
6565+ /// `Ok(None)` when the range is exhausted. Errors on any missing block
6666+ /// within the range, or on an MST node absent after the first in-range
6767+ /// record (which would leave the range unproven).
6868+ /// Yield the next in-range record.
6969+ ///
7070+ /// Transparently skips boundary items outside the range. Returns
7171+ /// `Ok(None)` when the range is exhausted — proof validation runs
7272+ /// automatically before returning `None`, so the `while let` pattern
7373+ /// is sufficient:
7474+ ///
7575+ /// ```ignore
7676+ /// while let Some(output) = walker.next()? { ... }
7777+ /// // proof has been validated; any violation surfaces as Err before None
7878+ /// ```
7979+ ///
8080+ /// Errors on any missing block within the range, on an MST node absent
8181+ /// after the first in-range record, or on a proof violation.
8282+ pub fn next(&mut self) -> Result<Option<Output>, SliceError> {
8383+ if matches!(self.state, SliceState::Done) {
8484+ return Ok(None);
8585+ }
8686+ loop {
8787+ match self.mem_car.next()? {
8888+ None => {
8989+ self.state = SliceState::Done;
9090+ validate_lower(self.preceding_key.as_deref(), &self.lower)?;
9191+ validate_upper(self.following_key.as_deref(), &self.upper)?;
9292+ return Ok(None);
9393+ }
9494+ Some(WalkItem::MissingSubtree { cid }) => {
9595+ if matches!(self.state, SliceState::In) {
9696+ return Err(SliceError::MissingNode { cid });
9797+ }
9898+ // Before: boundary subtree outside the range, skip
9999+ }
100100+ Some(WalkItem::MissingRecord { key, cid }) => {
101101+ if is_before(&key, &self.lower) {
102102+ self.preceding_key = Some(key);
103103+ } else if is_after(&key, &self.upper) {
104104+ self.following_key = Some(key);
105105+ self.state = SliceState::Done;
106106+ validate_lower(self.preceding_key.as_deref(), &self.lower)?;
107107+ validate_upper(self.following_key.as_deref(), &self.upper)?;
108108+ return Ok(None);
109109+ } else {
110110+ return Err(SliceError::IncompleteRange { key, cid });
111111+ }
112112+ }
113113+ Some(WalkItem::Record(out)) => {
114114+ if is_before(&out.key, &self.lower) {
115115+ self.preceding_key = Some(out.key);
116116+ } else if is_after(&out.key, &self.upper) {
117117+ self.following_key = Some(out.key);
118118+ self.state = SliceState::Done;
119119+ validate_lower(self.preceding_key.as_deref(), &self.lower)?;
120120+ validate_upper(self.following_key.as_deref(), &self.upper)?;
121121+ return Ok(None);
122122+ } else {
123123+ self.state = SliceState::In;
124124+ return Ok(Some(out));
125125+ }
126126+ }
127127+ }
128128+ }
129129+ }
130130+131131+ /// Drive any remaining walk to completion and return the proof keys.
132132+ ///
133133+ /// Useful when breaking out of the [`next`] loop early and still wanting
134134+ /// the proof. Drives remaining boundary items (O(log n) at most), with
135135+ /// proof validation happening inside `next` as usual.
136136+ pub fn finish(mut self) -> Result<SliceProof, SliceError> {
137137+ while self.next()?.is_some() {}
138138+ Ok(SliceProof {
139139+ preceding_key: self.preceding_key,
140140+ following_key: self.following_key,
141141+ })
142142+ }
143143+}
144144+145145+impl MemCar {
146146+ /// Walk a proven range of the MST.
147147+ ///
148148+ /// Returns a [`SliceWalker`] that yields records within `range` in key
149149+ /// order. After the loop, call [`SliceWalker::finish`] to validate that
150150+ /// the adjacent keys bound the range correctly.
151151+ ///
152152+ /// Accepts standard Rust range expressions:
153153+ /// - `"a".."b"` — exclusive upper bound
154154+ /// - `"a"..="b"` — inclusive upper bound
155155+ /// - `"a"..` — from `a` to end of tree
156156+ /// - `.."b"` — from start of tree to just before `b`
157157+ /// - `..` — entire tree
158158+ pub fn walk_slice<'r>(&mut self, range: impl RangeBounds<&'r str>) -> SliceWalker<'_> {
159159+ let lower = bound_to_owned(range.start_bound());
160160+ let upper = bound_to_owned(range.end_bound());
161161+ SliceWalker {
162162+ mem_car: self,
163163+ lower,
164164+ upper,
165165+ preceding_key: None,
166166+ following_key: None,
167167+ state: SliceState::Before,
168168+ }
169169+ }
170170+}
171171+172172+// ---------------------------------------------------------------------------
173173+// Helpers
174174+// ---------------------------------------------------------------------------
175175+176176+fn bound_to_owned(b: Bound<&&str>) -> Bound<String> {
177177+ match b {
178178+ Bound::Unbounded => Bound::Unbounded,
179179+ Bound::Included(s) => Bound::Included((*s).to_owned()),
180180+ Bound::Excluded(s) => Bound::Excluded((*s).to_owned()),
181181+ }
182182+}
183183+184184+fn is_before(key: &str, lower: &Bound<String>) -> bool {
185185+ match lower {
186186+ Bound::Unbounded => false,
187187+ Bound::Included(l) => key < l.as_str(),
188188+ Bound::Excluded(l) => key <= l.as_str(),
189189+ }
190190+}
191191+192192+fn is_after(key: &str, upper: &Bound<String>) -> bool {
193193+ match upper {
194194+ Bound::Unbounded => false,
195195+ Bound::Included(u) => key > u.as_str(),
196196+ Bound::Excluded(u) => key >= u.as_str(),
197197+ }
198198+}
199199+200200+fn validate_lower(preceding: Option<&str>, lower: &Bound<String>) -> Result<(), SliceError> {
201201+ let ok = match (preceding, lower) {
202202+ (None, _) => true,
203203+ (Some(p), Bound::Unbounded) => {
204204+ unreachable!("is_before always false for Unbounded, but got {p:?}")
205205+ }
206206+ (Some(p), Bound::Included(l)) => p < l.as_str(),
207207+ (Some(p), Bound::Excluded(l)) => p <= l.as_str(),
208208+ };
209209+ if ok {
210210+ Ok(())
211211+ } else {
212212+ Err(SliceError::BadPrecedingKey {
213213+ preceding: preceding.unwrap().to_owned(),
214214+ })
215215+ }
216216+}
217217+218218+fn validate_upper(following: Option<&str>, upper: &Bound<String>) -> Result<(), SliceError> {
219219+ let ok = match (following, upper) {
220220+ (None, _) => true,
221221+ (Some(f), Bound::Unbounded) => {
222222+ unreachable!("is_after always false for Unbounded, but got {f:?}")
223223+ }
224224+ (Some(f), Bound::Included(u)) => f > u.as_str(),
225225+ (Some(f), Bound::Excluded(u)) => f >= u.as_str(),
226226+ };
227227+ if ok {
228228+ Ok(())
229229+ } else {
230230+ Err(SliceError::BadFollowingKey {
231231+ following: following.unwrap().to_owned(),
232232+ })
233233+ }
234234+}