Skip to main content

nodedb_vector/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Vector engine error types.
4
5use nodedb_mem::MemError;
6
7/// Errors from vector index operations.
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum VectorError {
11    #[error("memory budget exhausted: {0}")]
12    BudgetExhausted(#[from] MemError),
13    /// An input vector — a search query or an inserted vector — has a
14    /// different dimension from the index. The caller's input is wrong; the
15    /// index is intact.
16    #[error("vector dimension mismatch: expected {expected}, got {got}")]
17    DimensionMismatch { expected: usize, got: usize },
18    /// Stored data — a PQ code, a segment backing, a materialized node
19    /// vector — disagrees with the index dimension. The stored data is
20    /// corrupt or belongs to another index.
21    #[error("stored vector data has dimension {got}, index expects {expected}")]
22    StoredDimensionMismatch { expected: usize, got: usize },
23    /// A serialized pre-filter bitmap does not decode. Searching without it
24    /// would return rows the filter excludes, so the search fails instead.
25    #[error("vector search filter bitmap does not decode: {detail}")]
26    InvalidFilterBitmap { detail: String },
27    /// An index build or training call received input it cannot use: an
28    /// empty training set, a zero dimension, or parameters that do not fit.
29    #[error("invalid vector index input: {detail}")]
30    InvalidInput { detail: String },
31    /// A node's vector could not be materialized: the node is out of range, or
32    /// its local storage is empty and no segment backing supplies the data.
33    ///
34    /// An empty local storage means the index was restored from a graph-only
35    /// checkpoint; the vectors live in an external segment that must be
36    /// attached with [`crate::HnswIndex::with_backing`] before any caller
37    /// copies vectors out of the index.
38    #[error(
39        "vector for node {id} is unavailable: node storage is empty and no \
40         segment backing provides it (graph-only checkpoint without backing?)"
41    )]
42    VectorUnavailable { id: u32 },
43    /// A node's dtype-encoded bytes could not be decoded to f32.
44    #[error("vector decode failed for node {id}: {detail}")]
45    VectorDecodeFailed { id: u32, detail: String },
46    #[error("unsupported HNSW checkpoint version {found}; expected {expected}")]
47    UnsupportedVersion { found: u8, expected: u8 },
48    #[error("invalid PQ codec magic bytes")]
49    InvalidMagic,
50    #[error("PQ codec deserialization failed: {0}")]
51    DeserializationFailed(String),
52    /// Checkpoint file is encrypted (starts with `SEGV`) but no KEK was supplied.
53    #[error(
54        "vector checkpoint is encrypted but no encryption key was provided; \
55         cannot load plaintext from an encrypted checkpoint"
56    )]
57    CheckpointEncryptedNoKey,
58    /// Checkpoint file is plaintext but a KEK was configured (policy violation).
59    #[error(
60        "vector checkpoint is plaintext but an encryption key is configured; \
61         refusing to load an unencrypted checkpoint when encryption is required"
62    )]
63    CheckpointPlaintextKeyRequired,
64    /// AES-256-GCM encryption/decryption or envelope framing of a checkpoint failed.
65    #[error("vector checkpoint encryption error: {detail}")]
66    CheckpointEncryptionError { detail: String },
67    /// rkyv or MessagePack serialization of a vector checkpoint failed.
68    #[error("vector checkpoint serialization error: {detail}")]
69    CheckpointSerializationError { detail: String },
70    /// rkyv or MessagePack deserialization of a vector checkpoint failed.
71    #[error("vector checkpoint deserialization error: {detail}")]
72    CheckpointDeserializationError { detail: String },
73    /// I/O error from segment file operations (open, mmap, metadata).
74    #[error("vector segment I/O error: {0}")]
75    SegmentIo(#[from] std::io::Error),
76}
77
78/// Check that an input vector of length `got` fits an index of dimension
79/// `expected`.
80pub fn check_dim(expected: usize, got: usize) -> Result<(), VectorError> {
81    if expected == got {
82        Ok(())
83    } else {
84        Err(VectorError::DimensionMismatch { expected, got })
85    }
86}