vecstore/
error.rs

1//! Error types for vecstore
2//!
3//! This module provides a comprehensive error type system for all vecstore operations.
4//! Instead of using generic `anyhow::Result`, we use strongly-typed errors that provide
5//! better debugging information and allow users to handle specific error cases.
6
7use std::io;
8use std::path::PathBuf;
9use thiserror::Error;
10
11/// Result type alias for vecstore operations
12pub type Result<T> = std::result::Result<T, VecStoreError>;
13
14/// Main error type for all vecstore operations
15#[derive(Error, Debug)]
16pub enum VecStoreError {
17    /// I/O errors (file operations, network, etc.)
18    #[error("I/O error: {0}")]
19    Io(#[from] io::Error),
20
21    /// Serialization/deserialization errors
22    #[error("Serialization error: {0}")]
23    Serialization(String),
24
25    /// Invalid vector dimension
26    #[error("Invalid vector dimension: expected {expected}, got {actual}")]
27    DimensionMismatch { expected: usize, actual: usize },
28
29    /// Vector not found
30    #[error("Vector with id '{id}' not found")]
31    VectorNotFound { id: String },
32
33    /// Invalid filter expression
34    #[error("Invalid filter expression: {0}")]
35    InvalidFilter(String),
36
37    /// Filter parsing error
38    #[error("Filter parse error at position {position}: {message}")]
39    FilterParse { position: usize, message: String },
40
41    /// Index not initialized
42    #[error("Index not initialized or corrupted")]
43    IndexNotInitialized,
44
45    /// Invalid configuration
46    #[error("Invalid configuration: {0}")]
47    InvalidConfig(String),
48
49    /// Database corruption
50    #[error("Database corruption detected at {path:?}: {reason}")]
51    Corruption { path: PathBuf, reason: String },
52
53    /// Snapshot errors
54    #[error("Snapshot error: {0}")]
55    Snapshot(String),
56
57    /// Snapshot not found
58    #[error("Snapshot '{name}' not found")]
59    SnapshotNotFound { name: String },
60
61    /// HNSW index errors
62    #[error("HNSW index error: {0}")]
63    HnswError(String),
64
65    /// Product Quantization errors
66    #[error("Product Quantization error: {0}")]
67    PqError(String),
68
69    /// Quantizer not trained
70    #[error("Quantizer not trained - call train() first")]
71    QuantizerNotTrained,
72
73    /// Insufficient training data
74    #[error("Insufficient training data: need at least {required}, got {actual}")]
75    InsufficientTrainingData { required: usize, actual: usize },
76
77    /// Hybrid search error
78    #[error("Hybrid search error: {0}")]
79    HybridSearch(String),
80
81    /// Text not indexed for hybrid search
82    #[error("Text not indexed for id '{id}' - call index_text() first")]
83    TextNotIndexed { id: String },
84
85    /// Empty query
86    #[error("Query cannot be empty")]
87    EmptyQuery,
88
89    /// Invalid parameter
90    #[error("Invalid parameter '{param}': {reason}")]
91    InvalidParameter { param: String, reason: String },
92
93    /// Memory limit exceeded
94    #[error("Memory limit exceeded: {current} bytes (limit: {limit} bytes)")]
95    MemoryLimitExceeded { current: usize, limit: usize },
96
97    /// Concurrent access error
98    #[error("Concurrent access error: {0}")]
99    ConcurrentAccess(String),
100
101    /// Lock error (poisoned lock)
102    #[error("Lock error: {0}")]
103    LockError(String),
104
105    /// Embedding errors
106    #[cfg(feature = "embeddings")]
107    #[error("Embedding error: {0}")]
108    Embedding(String),
109
110    /// ONNX Runtime errors
111    #[cfg(feature = "embeddings")]
112    #[error("ONNX Runtime error: {0}")]
113    OnnxRuntime(String),
114
115    /// Tokenization errors
116    #[cfg(feature = "embeddings")]
117    #[error("Tokenization error: {0}")]
118    Tokenization(String),
119
120    /// Python binding errors
121    #[cfg(feature = "python")]
122    #[error("Python binding error: {0}")]
123    Python(String),
124
125    /// WASM specific errors
126    #[cfg(feature = "wasm")]
127    #[error("WASM error: {0}")]
128    Wasm(String),
129
130    /// Feature not enabled
131    #[error("Feature '{feature}' not enabled - compile with --features {feature}")]
132    FeatureNotEnabled { feature: String },
133
134    /// Other errors
135    #[error("Error: {0}")]
136    Other(String),
137}
138
139// Implement conversions from various error types
140
141impl From<bincode::Error> for VecStoreError {
142    fn from(err: bincode::Error) -> Self {
143        VecStoreError::Serialization(err.to_string())
144    }
145}
146
147impl From<serde_json::Error> for VecStoreError {
148    fn from(err: serde_json::Error) -> Self {
149        VecStoreError::Serialization(err.to_string())
150    }
151}
152
153#[cfg(feature = "embeddings")]
154impl From<ort::OrtError> for VecStoreError {
155    fn from(err: ort::OrtError) -> Self {
156        VecStoreError::OnnxRuntime(err.to_string())
157    }
158}
159
160#[cfg(feature = "embeddings")]
161impl From<tokenizers::Error> for VecStoreError {
162    fn from(err: tokenizers::Error) -> Self {
163        VecStoreError::Tokenization(err.to_string())
164    }
165}
166
167impl<T> From<std::sync::PoisonError<T>> for VecStoreError {
168    fn from(err: std::sync::PoisonError<T>) -> Self {
169        VecStoreError::LockError(err.to_string())
170    }
171}
172
173// Helper functions for creating common errors
174
175impl VecStoreError {
176    /// Create a dimension mismatch error
177    pub fn dimension_mismatch(expected: usize, actual: usize) -> Self {
178        VecStoreError::DimensionMismatch { expected, actual }
179    }
180
181    /// Create a vector not found error
182    pub fn vector_not_found(id: impl Into<String>) -> Self {
183        VecStoreError::VectorNotFound { id: id.into() }
184    }
185
186    /// Create an invalid filter error
187    pub fn invalid_filter(msg: impl Into<String>) -> Self {
188        VecStoreError::InvalidFilter(msg.into())
189    }
190
191    /// Create a filter parse error
192    pub fn filter_parse(position: usize, message: impl Into<String>) -> Self {
193        VecStoreError::FilterParse {
194            position,
195            message: message.into(),
196        }
197    }
198
199    /// Create an invalid config error
200    pub fn invalid_config(msg: impl Into<String>) -> Self {
201        VecStoreError::InvalidConfig(msg.into())
202    }
203
204    /// Create a corruption error
205    pub fn corruption(path: impl Into<PathBuf>, reason: impl Into<String>) -> Self {
206        VecStoreError::Corruption {
207            path: path.into(),
208            reason: reason.into(),
209        }
210    }
211
212    /// Create a snapshot error
213    pub fn snapshot(msg: impl Into<String>) -> Self {
214        VecStoreError::Snapshot(msg.into())
215    }
216
217    /// Create a snapshot not found error
218    pub fn snapshot_not_found(name: impl Into<String>) -> Self {
219        VecStoreError::SnapshotNotFound { name: name.into() }
220    }
221
222    /// Create an HNSW error
223    pub fn hnsw_error(msg: impl Into<String>) -> Self {
224        VecStoreError::HnswError(msg.into())
225    }
226
227    /// Create a PQ error
228    pub fn pq_error(msg: impl Into<String>) -> Self {
229        VecStoreError::PqError(msg.into())
230    }
231
232    /// Create insufficient training data error
233    pub fn insufficient_training_data(required: usize, actual: usize) -> Self {
234        VecStoreError::InsufficientTrainingData { required, actual }
235    }
236
237    /// Create a hybrid search error
238    pub fn hybrid_search(msg: impl Into<String>) -> Self {
239        VecStoreError::HybridSearch(msg.into())
240    }
241
242    /// Create a text not indexed error
243    pub fn text_not_indexed(id: impl Into<String>) -> Self {
244        VecStoreError::TextNotIndexed { id: id.into() }
245    }
246
247    /// Create an invalid parameter error
248    pub fn invalid_parameter(param: impl Into<String>, reason: impl Into<String>) -> Self {
249        VecStoreError::InvalidParameter {
250            param: param.into(),
251            reason: reason.into(),
252        }
253    }
254
255    /// Create a memory limit exceeded error
256    pub fn memory_limit_exceeded(current: usize, limit: usize) -> Self {
257        VecStoreError::MemoryLimitExceeded { current, limit }
258    }
259
260    /// Create a concurrent access error
261    pub fn concurrent_access(msg: impl Into<String>) -> Self {
262        VecStoreError::ConcurrentAccess(msg.into())
263    }
264
265    /// Create a feature not enabled error
266    pub fn feature_not_enabled(feature: impl Into<String>) -> Self {
267        VecStoreError::FeatureNotEnabled {
268            feature: feature.into(),
269        }
270    }
271
272    /// Create a generic error
273    pub fn other(msg: impl Into<String>) -> Self {
274        VecStoreError::Other(msg.into())
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_dimension_mismatch() {
284        let err = VecStoreError::dimension_mismatch(128, 256);
285        assert_eq!(
286            err.to_string(),
287            "Invalid vector dimension: expected 128, got 256"
288        );
289    }
290
291    #[test]
292    fn test_vector_not_found() {
293        let err = VecStoreError::vector_not_found("vec_123");
294        assert_eq!(err.to_string(), "Vector with id 'vec_123' not found");
295    }
296
297    #[test]
298    fn test_filter_parse() {
299        let err = VecStoreError::filter_parse(42, "unexpected token");
300        assert_eq!(
301            err.to_string(),
302            "Filter parse error at position 42: unexpected token"
303        );
304    }
305
306    #[test]
307    fn test_memory_limit_exceeded() {
308        let err = VecStoreError::memory_limit_exceeded(1000000, 500000);
309        assert_eq!(
310            err.to_string(),
311            "Memory limit exceeded: 1000000 bytes (limit: 500000 bytes)"
312        );
313    }
314
315    #[test]
316    fn test_feature_not_enabled() {
317        let err = VecStoreError::feature_not_enabled("embeddings");
318        assert_eq!(
319            err.to_string(),
320            "Feature 'embeddings' not enabled - compile with --features embeddings"
321        );
322    }
323}