Skip to main content

iqdb_types/
id.rs

1//! Stable identifiers for stored vectors.
2//!
3//! A [`VectorId`] is either a compact 64-bit integer or an opaque byte key, so
4//! a caller can use whichever id space their data already has (a row id, a
5//! content hash, a UUID's bytes) without the iqdb spine imposing one.
6
7use core::fmt;
8
9use crate::error::IqdbError;
10
11/// A stable identifier for a stored vector.
12///
13/// Either a compact [`U64`](VectorId::U64) integer id or an opaque
14/// [`Bytes`](VectorId::Bytes) key. Build a `U64` id with
15/// [`From<u64>`](VectorId::from); build a `Bytes` id with
16/// [`TryFrom<Vec<u8>>`](VectorId::try_from), which rejects an empty key.
17///
18/// # Examples
19///
20/// ```
21/// use iqdb_types::VectorId;
22///
23/// let numeric = VectorId::from(7u64);
24/// assert_eq!(numeric, VectorId::U64(7));
25///
26/// let key = VectorId::try_from(vec![0xde, 0xad]).expect("non-empty key");
27/// assert_eq!(key, VectorId::Bytes(vec![0xde, 0xad].into_boxed_slice()));
28///
29/// // An empty byte key is rejected.
30/// assert!(VectorId::try_from(Vec::new()).is_err());
31/// ```
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub enum VectorId {
35    /// A compact 64-bit integer id (for example, a row id).
36    U64(u64),
37    /// An opaque, non-empty byte key (for example, a content hash or a UUID's
38    /// raw bytes).
39    Bytes(Box<[u8]>),
40}
41
42impl From<u64> for VectorId {
43    #[inline]
44    fn from(value: u64) -> Self {
45        Self::U64(value)
46    }
47}
48
49impl TryFrom<Vec<u8>> for VectorId {
50    type Error = IqdbError;
51
52    /// Builds a [`Bytes`](VectorId::Bytes) id, rejecting an empty key with
53    /// [`IqdbError::InvalidConfig`] (a malformed identifier is a
54    /// configuration shape problem, not a malformed vector).
55    #[inline]
56    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
57        if bytes.is_empty() {
58            return Err(IqdbError::InvalidConfig {
59                reason: "VectorId::Bytes key must not be empty",
60            });
61        }
62        Ok(Self::Bytes(bytes.into_boxed_slice()))
63    }
64}
65
66/// Operator-facing rendering: `U64` is the decimal integer; `Bytes` is
67/// lowercase hex with no prefix and no separators (so a 32-byte
68/// SHA-256-shaped id renders as 64 hex characters). `Debug` keeps the
69/// `Bytes([...])` shape for in-source troubleshooting; `Display` is the
70/// shape that lands in operator-facing logs and error messages.
71///
72/// # Examples
73///
74/// ```
75/// use iqdb_types::VectorId;
76///
77/// assert_eq!(VectorId::from(7u64).to_string(), "7");
78///
79/// let key = VectorId::try_from(vec![0xde, 0xad, 0xbe, 0xef]).expect("non-empty");
80/// assert_eq!(key.to_string(), "deadbeef");
81/// ```
82impl fmt::Display for VectorId {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Self::U64(n) => write!(f, "{n}"),
86            Self::Bytes(bytes) => {
87                for byte in bytes.iter() {
88                    write!(f, "{byte:02x}")?;
89                }
90                Ok(())
91            }
92        }
93    }
94}