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}