Skip to main content

diskann_record/load/
context.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6//! Load-side context, object, array, and side-car reader.
7//!
8//! [`Context`] is the cheap, clonable handle threaded through every
9//! [`super::Load::load`] / [`super::Loadable::load`] impl. From a [`Context`], loaders
10//! ask:
11//!
12//! * [`Context::as_object`] / [`Object::field`] for nested records.
13//! * [`Context::as_array`] / [`Array::iter`] for sequences.
14//! * [`Context::as_str`] / [`Context::as_number`] / [`Context::as_bool`] / [`Context::is_null`] for scalars.
15//! * [`Object::read`] for side-car artifacts referenced by a
16//!   [`save::Handle`](super::save::Handle).
17//!
18//! [`Reader`] implements [`std::io::Read`] and [`std::io::Seek`] over a side-car
19//! artifact, regardless of the provider's backing store.
20
21use std::io::BufReader;
22
23use crate::{
24    Number, Version,
25    load::{Loadable, Result, error},
26    save,
27};
28
29/// The backing store for a load operation.
30///
31/// A `LoadContext` supplies the root manifest [`save::Value`] ([`LoadContext::value`])
32/// and resolves side-car artifacts referenced by handles ([`LoadContext::read`]). The
33/// concrete implementations live under `crate::backend`: a disk-backed context (under
34/// the `disk` feature) and an in-memory context. Alternative implementations (e.g. a
35/// virtual filesystem) can be supplied for testing.
36///
37/// The generic [`load`](super::load) entry point is parameterized over this trait, and
38/// [`Context`] / [`Object`] / `Array` borrow it as an object-safe `&dyn LoadContext`
39/// so the load tree is agnostic to the concrete context type.
40pub(crate) trait LoadContext {
41    /// The root value of the manifest.
42    ///
43    /// # Errors
44    ///
45    /// Returns [`Error`](crate::load::Error) if the root value cannot be produced.
46    fn value(&self) -> Result<&save::Value<'_>>;
47
48    /// Open the side-car artifact identified by `key` for reading.
49    ///
50    /// # Errors
51    ///
52    /// Returns [`error::Kind::MissingFile`] if the file is not registered with this
53    /// context or if `key` escapes the manifest directory.
54    fn read(&self, key: &str) -> Result<Reader<'_>>;
55}
56
57/// The backend-specific half of a [`Reader`].
58///
59/// Each [`LoadContext`] implementation supplies its own `ReaderInner` (e.g. an in-memory
60/// cursor or an on-disk file). The blanket impl covers any type that is both
61/// [`std::io::Read`] and [`std::io::Seek`].
62pub(crate) trait ReaderInner: std::io::Read + std::io::Seek {}
63
64impl<T> ReaderInner for T where T: std::io::Read + std::io::Seek {}
65
66/// A borrowed reader over a side-car artifact.
67///
68/// Produced by [`Object::read`]. Implements [`std::io::Read`] and [`std::io::Seek`] over
69/// whatever backing store the `LoadContext` provides, so non-file-backed providers
70/// (like an in-memory byte buffer) can supply an arbitrary seekable reader.
71pub struct Reader<'a> {
72    io: BufReader<Box<dyn ReaderInner + 'a>>,
73}
74
75impl<'a> Reader<'a> {
76    /// Build a reader over an arbitrary borrowed [`ReaderInner`] source.
77    ///
78    /// Used by [`LoadContext`] implementations to expose a side-car artifact backed by a
79    /// file or an in-memory [`std::io::Cursor`].
80    pub(crate) fn new<T>(io: T) -> Self
81    where
82        T: ReaderInner + 'a,
83    {
84        Self {
85            io: BufReader::new(Box::new(io)),
86        }
87    }
88}
89
90impl std::io::Read for Reader<'_> {
91    // Required method
92    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
93        self.io.read(buf)
94    }
95
96    // Provided methods
97    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
98        self.io.read_vectored(bufs)
99    }
100    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
101        self.io.read_to_end(buf)
102    }
103    fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
104        self.io.read_to_string(buf)
105    }
106    fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
107        self.io.read_exact(buf)
108    }
109}
110
111impl std::io::Seek for Reader<'_> {
112    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
113        self.io.seek(pos)
114    }
115    fn rewind(&mut self) -> std::io::Result<()> {
116        self.io.rewind()
117    }
118    fn stream_position(&mut self) -> std::io::Result<u64> {
119        self.io.stream_position()
120    }
121    fn seek_relative(&mut self, offset: i64) -> std::io::Result<()> {
122        self.io.seek_relative(offset)
123    }
124}
125
126///////////////////////
127// User facing types //
128///////////////////////
129
130/// A cheap, clonable handle threaded through every load impl.
131///
132/// Loaders use the `as_*` accessors to peek at the underlying [`save::Value`] kind
133/// (e.g. [`Context::as_object`], [`Context::as_array`], [`Context::as_str`]) and
134/// [`Context::load`] to recursively deserialize a nested value into a concrete type.
135#[derive(Clone)]
136pub struct Context<'a> {
137    inner: &'a dyn LoadContext,
138    value: &'a save::Value<'a>,
139}
140
141impl<'a> Context<'a> {
142    pub(super) fn new(inner: &'a dyn LoadContext, value: &'a save::Value<'a>) -> Self {
143        Self { inner, value }
144    }
145
146    fn context(&self) -> &'a dyn LoadContext {
147        self.inner
148    }
149
150    /// Recursively deserialize the underlying value into a `T`.
151    ///
152    /// Equivalent to calling `T::load(self.clone())`. Use this from inner loaders that
153    /// want to delegate to another [`Loadable`].
154    pub fn load<T>(&self) -> Result<T>
155    where
156        T: Loadable<'a>,
157    {
158        T::load(self.clone())
159    }
160
161    /// Returns `Some(Object)` if the value is a versioned object, else `None`.
162    pub fn as_object(&self) -> Option<Object<'a>> {
163        match self.value {
164            save::Value::Object(versioned) => {
165                let object = Object {
166                    inner: self.inner,
167                    record: versioned.record(),
168                    version: versioned.version(),
169                };
170                Some(object)
171            }
172            _ => None,
173        }
174    }
175
176    /// Returns `Some(s)` if the value is a string, else `None`.
177    pub fn as_str(&self) -> Option<&'a str> {
178        match self.value {
179            save::Value::String(s) => Some(s),
180            _ => None,
181        }
182    }
183
184    /// Returns `Some(Array)` if the value is an array, else `None`.
185    pub fn as_array(&self) -> Option<Array<'a>> {
186        match self.value {
187            save::Value::Array(array) => Some(Array::new(self.context(), array)),
188            _ => None,
189        }
190    }
191
192    /// Returns `Some(Number)` if the value is numeric, else `None`.
193    ///
194    /// Use the conversion methods on [`Number`] (e.g. `as_u32`, `as_i64`) to narrow to
195    /// the target Rust type; out-of-range conversions return `None` and should be
196    /// surfaced as [`error::Kind::NumberOutOfRange`].
197    pub fn as_number(&self) -> Option<Number> {
198        match self.value {
199            save::Value::Number(number) => Some(*number),
200            _ => None,
201        }
202    }
203
204    /// Returns `Some(b)` if the value is a boolean, else `None`.
205    pub fn as_bool(&self) -> Option<bool> {
206        match self.value {
207            save::Value::Bool(value) => Some(*value),
208            _ => None,
209        }
210    }
211
212    /// Returns `true` if the value is null.
213    ///
214    /// Used by [`Loadable`] impls for [`Option<T>`] to detect the absent variant.
215    pub fn is_null(&self) -> bool {
216        matches!(self.value, save::Value::Null)
217    }
218
219    pub(crate) fn as_handle(&self) -> Option<&save::Handle> {
220        match self.value {
221            save::Value::Handle(handle) => Some(handle),
222            _ => None,
223        }
224    }
225}
226
227/// A versioned record reached through [`Context::as_object`].
228///
229/// `Object` is the entry point for record-based deserialization: it exposes the schema
230/// version via [`Object::version`], the user keys via [`Object::keys`], typed field
231/// extraction via [`Object::field`] (and the [`load_fields!`](crate::load_fields)
232/// macro), and side-car artifact access via [`Object::read`].
233pub struct Object<'a> {
234    inner: &'a dyn LoadContext,
235    record: &'a save::Record<'a>,
236    version: Version,
237}
238
239impl<'a> Object<'a> {
240    /// The schema [`Version`] recorded in the manifest for this object.
241    pub fn version(&self) -> Version {
242        self.version
243    }
244
245    /// Iterate over the user keys of this record. Reserved keys (`$version`,
246    /// `$handle`) are tracked separately and never appear here.
247    pub fn keys(&self) -> save::Keys<'_, 'a> {
248        self.record.keys()
249    }
250
251    /// Number of user keys in this record.
252    pub fn len(&self) -> usize {
253        self.record.len()
254    }
255
256    /// Whether this record has no user keys.
257    pub fn is_empty(&self) -> bool {
258        self.record.is_empty()
259    }
260
261    /// Return the sole user key of this record, used by enum loaders to dispatch
262    /// to a variant arm. Errors with a recoverable [`error::Kind::TypeMismatch`]
263    /// if the record has zero or multiple user keys (i.e. the wire shape does
264    /// not look like an enum).
265    pub fn single_key(&self) -> Result<&str> {
266        let mut keys = self.record.keys();
267        let Some(first) = keys.next() else {
268            return Err(error::Kind::TypeMismatch.into());
269        };
270        if keys.next().is_some() {
271            return Err(error::Kind::TypeMismatch.into());
272        }
273        Ok(first)
274    }
275
276    /// Descend into the raw [`Context`] for `key`, without imposing a type.
277    /// Useful for enum variants whose payload is itself an [`Object`], an array,
278    /// or any other [`save::Value`]. Returns [`error::Kind::MissingField`] when
279    /// the key is absent.
280    pub fn child(&self, key: &str) -> Result<Context<'a>> {
281        match self.record.get(key) {
282            Some(value) => Ok(Context::new(self.context(), value)),
283            None => Err(error::Kind::MissingField.into()),
284        }
285    }
286
287    /// Extract the value under `key` and deserialize it into a `T`.
288    ///
289    /// This is the typed counterpart to [`Object::child`] and the primitive used by the
290    /// [`load_fields!`](crate::load_fields) macro.
291    ///
292    /// # Errors
293    ///
294    /// Returns [`error::Kind::MissingField`] if the key is absent. Errors raised by
295    /// `T::load` (e.g. [`error::Kind::TypeMismatch`]) are propagated unchanged.
296    pub fn field<T>(&self, key: &str) -> Result<T>
297    where
298        T: Loadable<'a>,
299    {
300        match self.record.get(key) {
301            Some(value) => T::load(Context::new(self.context(), value)),
302            None => Err((error::Kind::MissingField).into()),
303        }
304    }
305
306    /// Open the side-car artifact identified by `handle` for reading.
307    ///
308    /// The handle must have been previously written through the matching
309    /// [`save::Context::write`](super::save::Context::write) call and embedded in this
310    /// record. Returns [`error::Kind::MissingFile`] if the file is not registered in the
311    /// manifest or if the handle attempts to escape the manifest directory.
312    pub fn read(&self, handle: &save::Handle) -> Result<Reader<'_>> {
313        self.inner.read(handle.as_str())
314    }
315
316    fn context(&self) -> &'a dyn LoadContext {
317        self.inner
318    }
319}
320
321/// A homogeneous sequence of values reached through [`Context::as_array`].
322///
323/// Backed by a borrowed `&[Value]`. Use [`Array::iter`] to walk the elements; each
324/// item is yielded as a [`Context`] that can be further deserialized via
325/// [`Context::load`].
326pub struct Array<'a> {
327    inner: &'a dyn LoadContext,
328    array: &'a [save::Value<'a>],
329}
330
331impl<'a> Array<'a> {
332    fn new(inner: &'a dyn LoadContext, array: &'a [save::Value<'a>]) -> Self {
333        Self { inner, array }
334    }
335
336    /// Number of elements in the array.
337    pub fn len(&self) -> usize {
338        self.array.len()
339    }
340
341    /// Returns `true` if the array is empty.
342    pub fn is_empty(&self) -> bool {
343        self.len() == 0
344    }
345
346    /// Iterate over the elements, each as a [`Context`] ready for further deserialization.
347    pub fn iter(&self) -> Iter<'a> {
348        Iter::new(self.context(), self.array.iter())
349    }
350
351    fn context(&self) -> &'a dyn LoadContext {
352        self.inner
353    }
354}
355
356/// Iterator returned by [`Array::iter`].
357pub struct Iter<'a> {
358    inner: &'a dyn LoadContext,
359    iter: std::slice::Iter<'a, save::Value<'a>>,
360}
361
362impl<'a> Iter<'a> {
363    fn new(inner: &'a dyn LoadContext, iter: std::slice::Iter<'a, save::Value<'a>>) -> Self {
364        Self { inner, iter }
365    }
366}
367
368impl<'a> Iterator for Iter<'a> {
369    type Item = Context<'a>;
370    fn next(&mut self) -> Option<Self::Item> {
371        self.iter
372            .next()
373            .map(|value| Context::new(self.inner, value))
374    }
375
376    fn size_hint(&self) -> (usize, Option<usize>) {
377        self.iter.size_hint()
378    }
379}
380
381impl ExactSizeIterator for Iter<'_> {}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386
387    /// Minimal [`LoadContext`] used to anchor a [`Context`] in tests; the `Object`
388    /// methods under test never touch the context's `value` / `read` operations.
389    struct TestContext;
390
391    impl LoadContext for TestContext {
392        fn value(&self) -> Result<&save::Value<'_>> {
393            unimplemented!("not exercised by Object accessor tests")
394        }
395
396        fn read(&self, _key: &str) -> Result<Reader<'_>> {
397            unimplemented!("not exercised by Object accessor tests")
398        }
399    }
400
401    #[test]
402    fn object_reports_populated_keys() {
403        let mut record = save::Record::empty();
404        record.insert("alpha", save::Value::Null).unwrap();
405        record.insert("beta", save::Value::Bool(true)).unwrap();
406        let value = record.into_value(Version::new(1, 0));
407
408        let inner = TestContext;
409        let object = Context::new(&inner, &value)
410            .as_object()
411            .expect("a versioned object value must yield an Object");
412
413        assert_eq!(object.len(), 2);
414        assert!(!object.is_empty());
415        let mut keys: Vec<&str> = object.keys().collect();
416        keys.sort_unstable();
417        assert_eq!(keys, ["alpha", "beta"]);
418    }
419
420    #[test]
421    fn object_reports_empty_record() {
422        let value = save::Record::empty().into_value(Version::new(1, 0));
423
424        let inner = TestContext;
425        let object = Context::new(&inner, &value)
426            .as_object()
427            .expect("a versioned object value must yield an Object");
428
429        assert_eq!(object.len(), 0);
430        assert!(object.is_empty());
431        assert_eq!(object.keys().count(), 0);
432    }
433}