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);