Skip to main content

diskann_record/load/
mod.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6//! # Loading Records from Disk
7//!
8//! This module mirrors the [`super::save`] side. User types implement [`Load`] (or, for
9//! primitive-like leaves, [`Loadable`]) and obtain an [`Object`] / [`Context`] from which
10//! they extract individual fields and side-car artifacts.
11//!
12//! The generic entry point is `load`; `load_from_disk` (available under the `disk`
13//! feature) is the disk-backed convenience wrapper that reads a manifest and dispatches
14//! into the user type's [`Load`] impl.
15//!
16//! # Reading Records
17//!
18//! The [`load_fields!`](crate::load_fields) macro is the idiomatic way to extract a fixed
19//! set of named fields from an [`Object`] into local bindings. It mirrors the structure
20//! of [`save_fields!`](crate::save_fields).
21//!
22//! # Version Dispatch
23//!
24//! Each [`Load`] impl declares a [`VERSION`](Load::VERSION). If the version stored in the
25//! manifest matches, [`Load::load`] is called. Otherwise [`Load::load_legacy`] is invoked
26//! so the impl can perform a custom upgrade; returning an
27//! [`error::Kind::UnknownVersion`] from `load_legacy` indicates the loader has no upgrade
28//! path for that schema.
29//!
30//! # Recoverable vs. Critical Errors
31//!
32//! Load errors are tagged as recoverable or critical. Probing call sites that try
33//! multiple loaders should only retry when [`Error::is_recoverable`] returns `true`. See
34//! [`error::Kind::is_recoverable`] for the classification.
35
36pub mod error;
37pub use error::{Error, Result};
38
39mod context;
40pub(crate) use context::LoadContext;
41pub use context::{Context, Object, Reader};
42
43#[cfg(feature = "disk")]
44use std::path::Path;
45
46use crate::{Version, save};
47
48/// Deserialize a `T` from `context`.
49///
50/// This is the generic entry point: it asks `context` for its root value and threads a
51/// [`Context`] borrowing `context` through `T`'s [`Loadable::load`] impl.
52/// `load_from_disk` is the disk-backed convenience wrapper available under the `disk`
53/// feature.
54///
55/// # Errors
56///
57/// Returns [`Error`] if the context cannot produce a root value or if `T`'s loader fails.
58pub(crate) fn load<'a, T, C>(context: &'a C) -> Result<T>
59where
60    T: Loadable<'a>,
61    C: LoadContext,
62{
63    let value = context.value()?;
64    T::load(Context::new(context, value))
65}
66
67/// Reload a value previously written by [`save::save_to_disk`].
68///
69/// `metadata` is the manifest JSON path produced by the saver, and `dir` is the
70/// directory holding any side-car artifacts.
71///
72/// # Errors
73///
74/// Returns [`Error`] if the manifest is missing or malformed, if a referenced artifact is
75/// missing, or if a user [`Load`] impl fails (e.g. due to a version mismatch with no
76/// upgrade path).
77#[cfg(feature = "disk")]
78pub fn load_from_disk<T>(metadata: &Path, dir: &Path) -> Result<T>
79where
80    T: for<'a> Loadable<'a>,
81{
82    let context = crate::backend::disk::DiskLoadContext::new(metadata, dir)?;
83    load(&context)
84}
85
86/// Implemented by user types that can be reloaded from a versioned [`Object`].
87///
88/// This is the symmetric counterpart to [`super::save::Save`]. Implementations describe
89/// how to reconstruct `Self` from the manifest representation, and how to upgrade
90/// records written by older schemas via [`Self::load_legacy`].
91///
92/// # Enums
93///
94/// Enum types dispatch on the single non-reserved key of the object (see
95/// [`Object::single_key`]) and recurse via [`Object::child`] into the payload.
96pub trait Load<'a>: Sized {
97    /// The schema version this impl was written against.
98    ///
99    /// Compared with the manifest's version to choose between [`Self::load`] and
100    /// [`Self::load_legacy`].
101    const VERSION: Version;
102
103    /// Reconstruct `Self` from an object whose `$version` matches [`Self::VERSION`].
104    fn load(object: Object<'a>) -> Result<Self>;
105
106    /// Reconstruct `Self` from an object whose `$version` does *not* match
107    /// [`Self::VERSION`].
108    ///
109    /// Implementations may either upgrade the older record or refuse with
110    /// [`error::Kind::UnknownVersion`] when no upgrade is possible.
111    fn load_legacy(object: Object<'a>) -> Result<Self>;
112}
113
114/// Implemented by any value that can be deserialized from a [`Context`].
115///
116/// This is the bottom of the trait hierarchy and is implemented for the same set of
117/// primitive-like types as [`super::save::Saveable`]. Most user types should implement
118/// [`Load`] (which gets a [`Loadable`] impl for free via the blanket below) rather than
119/// [`Loadable`] directly.
120pub trait Loadable<'a>: Sized {
121    /// Deserialize `Self` from a [`Context`].
122    fn load(context: Context<'a>) -> Result<Self>;
123}
124
125impl<'a, T> Loadable<'a> for T
126where
127    T: Load<'a>,
128{
129    fn load(context: Context<'a>) -> Result<Self> {
130        let object = context.as_object().ok_or(error::Kind::TypeMismatch)?;
131        let version = object.version();
132        if version == T::VERSION {
133            T::load(object)
134        } else {
135            T::load_legacy(object)
136        }
137    }
138}
139
140////////////
141// Macros //
142////////////
143
144/// Extract a fixed set of named fields from an [`Object`] into local bindings.
145///
146/// Each name in the list becomes a `let` binding of the same name. An optional `: T`
147/// suffix selects the [`Loadable`] target type; without it, type inference picks the
148/// type from the surrounding context. Errors from individual fields are propagated with
149/// `?`.
150///
151/// ```ignore
152/// load_fields!(object, [
153///     dim: usize,
154///     label,                       // type inferred
155///     vectors: save::Handle,
156/// ]);
157/// ```
158#[macro_export]
159macro_rules! load_fields {
160    (@field $object:ident, $field:ident: $T:ty) => {
161        let $field: $T = $object.field(stringify!($field))?;
162    };
163    (@field $object:ident, $field:ident) => {
164        let $field = $object.field(stringify!($field))?;
165    };
166    ($object:ident, [$($field:ident $(: $ty:ty)?),+ $(,)?]) => {
167        $(
168            $crate::load_fields!(@field $object, $field $(: $ty)?);
169        )+
170    };
171}
172
173///////////////
174// Bootstrap //
175///////////////
176
177impl<'a> Loadable<'a> for &'a str {
178    fn load(context: Context<'a>) -> Result<Self> {
179        context
180            .as_str()
181            .ok_or_else(|| error::Kind::TypeMismatch.into())
182    }
183}
184
185impl Loadable<'_> for String {
186    fn load(context: Context<'_>) -> Result<Self> {
187        context.load::<&str>().map(|s| s.into())
188    }
189}
190
191impl Loadable<'_> for save::Handle {
192    fn load(context: Context<'_>) -> Result<Self> {
193        context
194            .as_handle()
195            .cloned()
196            .ok_or_else(|| error::Kind::TypeMismatch.into())
197    }
198}
199
200impl Loadable<'_> for bool {
201    fn load(context: Context<'_>) -> Result<Self> {
202        context
203            .as_bool()
204            .ok_or_else(|| error::Kind::TypeMismatch.into())
205    }
206}
207
208impl<'a, T> Loadable<'a> for Option<T>
209where
210    T: Loadable<'a>,
211{
212    fn load(context: Context<'a>) -> Result<Self> {
213        if context.is_null() {
214            Ok(None)
215        } else {
216            T::load(context).map(Some)
217        }
218    }
219}
220
221impl<'a, T> Loadable<'a> for Vec<T>
222where
223    T: Loadable<'a>,
224{
225    fn load(context: Context<'a>) -> Result<Self> {
226        match context.as_array() {
227            Some(array) => array.iter().map(T::load).collect(),
228            None => Err((error::Kind::TypeMismatch).into()),
229        }
230    }
231}
232
233macro_rules! load_number {
234    ($T:ty) => {
235        impl Loadable<'_> for $T {
236            fn load(context: Context<'_>) -> Result<Self> {
237                match context.as_number() {
238                    Some(n) => n.try_into().map_err(|_| error::Kind::NumberOutOfRange.into()),
239                    None => Err((error::Kind::TypeMismatch).into()),
240                }
241            }
242        }
243    };
244    ($($Ts:ty),+ $(,)?) => {
245        $(load_number!($Ts);)+
246    }
247}
248
249load_number!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
250
251// NonZero* primitives are loaded by deserializing the inner numeric type and then
252// validating it is non-zero. A zero value produces a `NumberOutOfRange` light error.
253macro_rules! load_nonzero {
254    ($T:ty, $Inner:ty) => {
255        impl Loadable<'_> for $T {
256            fn load(context: Context<'_>) -> Result<Self> {
257                let inner: $Inner = context.load()?;
258                <$T>::new(inner).ok_or_else(|| error::Kind::NumberOutOfRange.into())
259            }
260        }
261    };
262}
263
264load_nonzero!(std::num::NonZeroU32, u32);
265load_nonzero!(std::num::NonZeroU64, u64);
266load_nonzero!(std::num::NonZeroUsize, usize);