Skip to main content

diskann_record/
value.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6//! Wire-level value types used in the on-disk manifest.
7//!
8//! These types are the shared currency of both halves of the framework:
9//! user value -> [`save`](crate::save) -> [`Value`] in the save path, and the
10//! [`Value`] -> [`load`](crate::load) -> user value in the load path.
11//!
12//! Every field stored in a manifest is one of:
13//!
14//! * [`Value::Null`] / [`Value::Bool`] / [`Value::Number`] / [`Value::String`] —
15//!   primitive scalars.
16//! * [`Value::Array`] — a homogeneous sequence (used by `Vec<T>` and `&[T]`).
17//! * [`Value::Object`] — a [`Versioned`] [`Record`] (the canonical encoding for a
18//!   `T: crate::save::Save`).
19//! * [`Value::Handle`] — a reference to a side-car artifact (produced by
20//!   [`crate::save::Context::write`] + [`crate::save::Writer::finish`]).
21//!
22//! Most user code never touches these enums directly. On the save side,
23//! [`crate::save::Saveable`] impls turn Rust values into [`Value`]s and the
24//! [`save_fields!`](crate::save_fields) macro assembles the surrounding [`Record`]; on
25//! the load side, the [`crate::load`] accessors walk the same [`Value`] tree back into
26//! Rust values.
27
28use std::{borrow::Cow, collections::HashMap};
29
30#[cfg(feature = "serde")]
31use serde::{
32    Deserialize, Deserializer, Serialize, Serializer,
33    de::{self, MapAccess, SeqAccess, Visitor},
34    ser::SerializeStruct,
35};
36
37use crate::{Number, Version, save::Error};
38
39/// The wire-level union of every saveable kind.
40///
41/// See the module-level docs for an overview of when each variant is produced. The
42/// borrowing parameter `'a` lets [`Value::String`] and nested
43/// records reuse memory owned by the caller without copying.
44#[derive(Debug)]
45pub enum Value<'a> {
46    Null,
47    Bool(bool),
48    Number(Number),
49    String(Cow<'a, str>),
50    Array(Vec<Value<'a>>),
51    Object(Versioned<'a>),
52    Handle(Handle),
53}
54
55#[cfg(feature = "serde")]
56impl Serialize for Value<'_> {
57    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
58        match self {
59            Self::Null => ser.serialize_none(),
60            Self::Bool(b) => ser.serialize_bool(*b),
61            Self::Number(n) => n.serialize(ser),
62            Self::String(s) => ser.serialize_str(s),
63            Self::Array(a) => a.serialize(ser),
64            Self::Object(v) => v.serialize(ser),
65            Self::Handle(h) => h.serialize(ser),
66        }
67    }
68}
69
70#[cfg(feature = "serde")]
71impl<'de> Deserialize<'de> for Value<'static> {
72    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73    where
74        D: Deserializer<'de>,
75    {
76        struct Inner;
77
78        impl<'de> Visitor<'de> for Inner {
79            type Value = Value<'static>;
80
81            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82                f.write_str("a valid Value")
83            }
84
85            fn visit_unit<E: de::Error>(self) -> Result<Value<'static>, E> {
86                Ok(Value::Null)
87            }
88
89            fn visit_none<E: de::Error>(self) -> Result<Value<'static>, E> {
90                Ok(Value::Null)
91            }
92
93            fn visit_some<D>(self, deserializer: D) -> Result<Value<'static>, D::Error>
94            where
95                D: Deserializer<'de>,
96            {
97                Value::deserialize(deserializer)
98            }
99
100            fn visit_bool<E: de::Error>(self, v: bool) -> Result<Value<'static>, E> {
101                Ok(Value::Bool(v))
102            }
103
104            fn visit_u64<E: de::Error>(self, v: u64) -> Result<Value<'static>, E> {
105                Ok(Value::Number(Number::U64(v)))
106            }
107
108            fn visit_i64<E: de::Error>(self, v: i64) -> Result<Value<'static>, E> {
109                Ok(Value::Number(Number::I64(v)))
110            }
111
112            fn visit_f64<E: de::Error>(self, v: f64) -> Result<Value<'static>, E> {
113                Ok(Value::Number(Number::F64(v)))
114            }
115
116            fn visit_str<E: de::Error>(self, v: &str) -> Result<Value<'static>, E> {
117                if let Some(n) = Number::from_sentinel(v) {
118                    return Ok(Value::Number(n));
119                }
120                Ok(Value::String(Cow::Owned(v.to_owned())))
121            }
122
123            fn visit_string<E: de::Error>(self, v: String) -> Result<Value<'static>, E> {
124                if let Some(n) = Number::from_sentinel(&v) {
125                    return Ok(Value::Number(n));
126                }
127                Ok(Value::String(Cow::Owned(v)))
128            }
129
130            fn visit_seq<A>(self, mut seq: A) -> Result<Value<'static>, A::Error>
131            where
132                A: SeqAccess<'de>,
133            {
134                let mut values = Vec::with_capacity(seq.size_hint().unwrap_or(0));
135                while let Some(v) = seq.next_element()? {
136                    values.push(v);
137                }
138                Ok(Value::Array(values))
139            }
140
141            fn visit_map<A>(self, mut map: A) -> Result<Value<'static>, A::Error>
142            where
143                A: MapAccess<'de>,
144            {
145                // TODO: Handle invariants that only one of our reserved words are present.
146                let mut version: Option<Version> = None;
147                let mut handle_name: Option<String> = None;
148                let mut fields: HashMap<Cow<'static, str>, Value<'static>> = HashMap::new();
149
150                while let Some(key) = map.next_key::<String>()? {
151                    match key.as_str() {
152                        "$version" => {
153                            version = Some(map.next_value()?);
154                        }
155                        "$handle" => {
156                            handle_name = Some(map.next_value()?);
157                        }
158                        _ => {
159                            let value = map.next_value()?;
160                            fields.insert(Cow::Owned(key), value);
161                        }
162                    }
163                }
164
165                if let Some(name) = handle_name {
166                    if version.is_some() || !fields.is_empty() {
167                        return Err(de::Error::custom(
168                            "handle object must contain only a \"$handle\" field",
169                        ));
170                    }
171                    return Ok(Value::Handle(Handle(name)));
172                }
173
174                if let Some(version) = version {
175                    let record = Record { record: fields };
176                    return Ok(Value::Object(Versioned { record, version }));
177                }
178
179                Err(de::Error::custom(
180                    "map must contain either \"$version\" or \"$handle\"",
181                ))
182            }
183        }
184
185        deserializer.deserialize_any(Inner)
186    }
187}
188
189impl From<Handle> for Value<'_> {
190    fn from(handle: Handle) -> Self {
191        Self::Handle(handle)
192    }
193}
194
195impl Value<'_> {
196    /// Convert this value into a fully owned [`Value<'static>`], deep-copying any borrowed
197    /// string or byte data.
198    ///
199    /// This is the allocation-based equivalent of round-tripping through the wire format:
200    /// it severs every borrow from the originating data so the result can be stored
201    /// independently of its source (for example inside an in-memory
202    /// [`crate::MemoryContext`]).
203    pub fn into_owned(self) -> Value<'static> {
204        match self {
205            Self::Null => Value::Null,
206            Self::Bool(b) => Value::Bool(b),
207            Self::Number(n) => Value::Number(n),
208            Self::String(s) => Value::String(Cow::Owned(s.into_owned())),
209            Self::Array(values) => {
210                Value::Array(values.into_iter().map(Value::into_owned).collect())
211            }
212            Self::Object(versioned) => Value::Object(versioned.into_owned()),
213            Self::Handle(handle) => Value::Handle(handle),
214        }
215    }
216}
217
218/// A map of named [`Value`]s.
219///
220/// `Record` is the body of an object in the manifest. On the save side each call to
221/// [`crate::save::Save::save`] returns one, and [`Record::into_value`] wraps it as a
222/// [`Versioned`] [`Value::Object`] ready for insertion into another record; on the load
223/// side the same record is read back through [`crate::load::Object`]. Keys beginning
224/// with `$` are reserved for framework metadata (see [`crate::is_reserved`]).
225#[derive(Debug)]
226#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
227#[cfg_attr(feature = "serde", serde(transparent))]
228pub struct Record<'a> {
229    record: HashMap<Cow<'a, str>, Value<'a>>,
230}
231
232impl<'a> Record<'a> {
233    /// Construct an empty record. Useful for unit enum variants.
234    pub fn empty() -> Self {
235        Self {
236            record: HashMap::new(),
237        }
238    }
239
240    /// Returns `true` if a value is registered under `key`.
241    pub fn contains_key(&self, key: &str) -> bool {
242        self.record.contains_key(key)
243    }
244
245    /// Look up the [`Value`] registered under `key`, if any.
246    pub fn get(&self, key: &str) -> Option<&Value<'a>> {
247        self.record.get(key)
248    }
249
250    /// Number of (user) keys in this record. Reserved keys (`$version`, `$handle`)
251    /// are tracked elsewhere and never appear here.
252    pub fn len(&self) -> usize {
253        self.record.len()
254    }
255
256    /// Returns `true` if this record has no user keys.
257    pub fn is_empty(&self) -> bool {
258        self.record.is_empty()
259    }
260
261    /// Iterate over the user keys in this record. Order is unspecified.
262    pub fn keys(&self) -> Keys<'_, 'a> {
263        Keys {
264            inner: self.record.keys(),
265        }
266    }
267
268    /// Insert `value` under `key`.
269    ///
270    /// # Errors
271    ///
272    /// Returns [`Error`] if `key` begins with `$`, which is reserved for the
273    /// save/load framework (see [`crate::is_reserved`]).
274    pub fn insert<K, V>(&mut self, key: K, value: V) -> crate::save::Result<Option<Value<'a>>>
275    where
276        K: Into<Cow<'a, str>>,
277        V: Into<Value<'a>>,
278    {
279        let key = key.into();
280        if crate::is_reserved(&key) {
281            return Err(Error::message(format!(
282                "record key {:?} is reserved (keys starting with `$` are reserved for the \
283                 save/load framework)",
284                key,
285            )));
286        }
287
288        Ok(self.record.insert(key, value.into()))
289    }
290
291    /// Wrap this record as a versioned [`Value`] ready for insertion into another
292    /// record. Use this from enum [`Save`](crate::save::Save) impls to attach the
293    /// outer type's version to an inline variant payload.
294    pub fn into_value(self, version: Version) -> Value<'a> {
295        Value::Object(Versioned::new(self, version))
296    }
297
298    /// Convert this record into a fully owned [`Record<'static>`], deep-copying borrowed
299    /// keys and values. See [`Value::into_owned`].
300    pub fn into_owned(self) -> Record<'static> {
301        Record {
302            record: self
303                .record
304                .into_iter()
305                .map(|(key, value)| (Cow::Owned(key.into_owned()), value.into_owned()))
306                .collect(),
307        }
308    }
309}
310
311/// Iterator over the keys of a [`Record`].
312pub struct Keys<'r, 'a> {
313    inner: std::collections::hash_map::Keys<'r, Cow<'a, str>, Value<'a>>,
314}
315
316impl<'r, 'a> Iterator for Keys<'r, 'a> {
317    type Item = &'r str;
318
319    fn next(&mut self) -> Option<Self::Item> {
320        self.inner.next().map(|k| k.as_ref())
321    }
322
323    fn size_hint(&self) -> (usize, Option<usize>) {
324        self.inner.size_hint()
325    }
326}
327
328impl ExactSizeIterator for Keys<'_, '_> {}
329
330impl<'a> FromIterator<(Cow<'a, str>, Value<'a>)> for Record<'a> {
331    fn from_iter<I: IntoIterator<Item = (Cow<'a, str>, Value<'a>)>>(itr: I) -> Self {
332        Self {
333            record: itr.into_iter().collect(),
334        }
335    }
336}
337
338/// A [`Record`] paired with the schema [`Version`] used to produce it.
339///
340/// Serialized as a normal object plus a `$version` field on the wire. Constructed by
341/// [`Record::into_value`].
342#[derive(Debug)]
343#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
344pub struct Versioned<'a> {
345    #[cfg_attr(feature = "serde", serde(flatten))]
346    record: Record<'a>,
347    #[cfg_attr(feature = "serde", serde(rename = "$version"))]
348    version: Version,
349}
350
351impl<'a> Versioned<'a> {
352    pub(crate) fn new(record: Record<'a>, version: Version) -> Self {
353        Self { record, version }
354    }
355
356    pub(crate) fn version(&self) -> Version {
357        self.version
358    }
359
360    pub(crate) fn record(&self) -> &Record<'a> {
361        &self.record
362    }
363
364    pub(crate) fn into_owned(self) -> Versioned<'static> {
365        Versioned {
366            record: self.record.into_owned(),
367            version: self.version,
368        }
369    }
370}
371
372/// A reference to a side-car artifact in the manifest directory.
373///
374/// Produced by [`Writer::finish`](crate::save::Writer::finish) after a side-car write completes and
375/// inserted into a [`Record`] like any other value. Serializes as `{"$handle": "<name>"}`
376/// on the wire; the load side rehydrates it through
377/// [`crate::load::Object::read`].
378#[derive(Debug, Clone)]
379pub struct Handle(String);
380
381impl Handle {
382    pub(crate) fn new(string: String) -> Self {
383        Self(string)
384    }
385
386    pub(crate) fn as_str(&self) -> &str {
387        &self.0
388    }
389}
390
391#[cfg(feature = "serde")]
392impl Serialize for Handle {
393    fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
394        let mut handle = ser.serialize_struct("Handle", 1)?;
395        handle.serialize_field("$handle", &self.0)?;
396        handle.end()
397    }
398}
399
400#[cfg(feature = "serde")]
401impl<'de> Deserialize<'de> for Handle {
402    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
403    where
404        D: Deserializer<'de>,
405    {
406        #[derive(Deserialize)]
407        struct Helper {
408            #[serde(rename = "$handle")]
409            handle: String,
410        }
411        let helper = Helper::deserialize(deserializer)?;
412        Ok(Handle(helper.handle))
413    }
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419
420    #[test]
421    fn insert_rejects_reserved_key() {
422        let mut record = Record::empty();
423        record
424            .insert("$version", Value::Null)
425            .expect_err("reserved key must be rejected");
426        record
427            .insert("ok", Value::Bool(true))
428            .expect("normal key must be accepted");
429        assert!(record.contains_key("ok"));
430        assert_eq!(record.len(), 1);
431    }
432
433    #[cfg(feature = "disk")]
434    #[test]
435    fn deserialize_rejects_handle_with_extra_fields() {
436        let json = r#"{ "$handle": "a.bin", "$version": "0.0" }"#;
437        serde_json::from_str::<Value<'static>>(json)
438            .expect_err("handle object with extra fields must be rejected");
439    }
440
441    #[cfg(feature = "disk")]
442    #[test]
443    fn deserialize_rejects_object_without_version_or_handle() {
444        let json = r#"{ "field": 1 }"#;
445        serde_json::from_str::<Value<'static>>(json)
446            .expect_err("object without $version or $handle must be rejected");
447    }
448
449    // The non-finite float sentinels (`"nan"`, `"inf"`, `"neg_inf"`) are reserved wire
450    // tokens: a JSON string equal to one of them always deserializes back as the
451    // corresponding `Number::F64`, never as a `Value::String`. This is intentional — those
452    // exact strings are not used as user string values in this repository, so the
453    // round-trip ambiguity is resolved in favor of the float sentinel.
454    #[cfg(feature = "disk")]
455    #[test]
456    fn float_sentinel_strings_deserialize_as_numbers() {
457        for (s, is_nan) in [("nan", true), ("inf", false), ("neg_inf", false)] {
458            let json = serde_json::to_string(&Value::String(Cow::Borrowed(s))).unwrap();
459            let back: Value<'static> = serde_json::from_str(&json).unwrap();
460            match back {
461                Value::Number(Number::F64(v)) if is_nan => assert!(v.is_nan()),
462                Value::Number(Number::F64(_)) => {}
463                other => panic!("sentinel {s:?} deserialized as {other:?}, expected Number::F64"),
464            }
465        }
466    }
467}