sevensense_vector/domain/
error.rs

1//! Error types for the Vector Space bounded context.
2
3use std::path::PathBuf;
4use thiserror::Error;
5
6use super::entities::{ConfigValidationError, EmbeddingId};
7
8/// Main error type for vector operations.
9#[derive(Debug, Error)]
10pub enum VectorError {
11    /// Dimension mismatch between vector and index.
12    #[error("Dimension mismatch: expected {expected}, got {got}")]
13    DimensionMismatch {
14        /// Expected dimensions from index configuration.
15        expected: usize,
16        /// Actual dimensions of the provided vector.
17        got: usize,
18    },
19
20    /// Vector with this ID already exists.
21    #[error("Vector with ID {0} already exists")]
22    DuplicateId(EmbeddingId),
23
24    /// Vector with this ID was not found.
25    #[error("Vector with ID {0} not found")]
26    NotFound(EmbeddingId),
27
28    /// Index capacity exceeded.
29    #[error("Index capacity exceeded: max {max}, current {current}")]
30    CapacityExceeded {
31        /// Maximum capacity.
32        max: usize,
33        /// Current size.
34        current: usize,
35    },
36
37    /// Invalid vector data (e.g., contains NaN or Inf).
38    #[error("Invalid vector data: {0}")]
39    InvalidVector(String),
40
41    /// Configuration error.
42    #[error("Configuration error: {0}")]
43    ConfigError(#[from] ConfigValidationError),
44
45    /// Index is empty.
46    #[error("Index is empty")]
47    EmptyIndex,
48
49    /// Serialization error.
50    #[error("Serialization error: {0}")]
51    SerializationError(String),
52
53    /// IO error during persistence.
54    #[error("IO error: {0}")]
55    IoError(#[from] std::io::Error),
56
57    /// File not found.
58    #[error("File not found: {0}")]
59    FileNotFound(PathBuf),
60
61    /// Corrupted index file.
62    #[error("Corrupted index file: {0}")]
63    CorruptedFile(String),
64
65    /// Lock acquisition failed.
66    #[error("Failed to acquire lock: {0}")]
67    LockError(String),
68
69    /// Operation timeout.
70    #[error("Operation timed out after {0}ms")]
71    Timeout(u64),
72
73    /// Index not initialized.
74    #[error("Index not initialized")]
75    NotInitialized,
76
77    /// Concurrent modification detected.
78    #[error("Concurrent modification detected")]
79    ConcurrentModification,
80
81    /// Graph operation error.
82    #[error("Graph error: {0}")]
83    GraphError(String),
84
85    /// Search parameters invalid.
86    #[error("Invalid search parameters: {0}")]
87    InvalidSearchParams(String),
88
89    /// Internal error (should not happen in normal operation).
90    #[error("Internal error: {0}")]
91    Internal(String),
92}
93
94impl VectorError {
95    /// Create a dimension mismatch error.
96    pub fn dimension_mismatch(expected: usize, got: usize) -> Self {
97        Self::DimensionMismatch { expected, got }
98    }
99
100    /// Create a capacity exceeded error.
101    pub fn capacity_exceeded(max: usize, current: usize) -> Self {
102        Self::CapacityExceeded { max, current }
103    }
104
105    /// Create an invalid vector error.
106    pub fn invalid_vector(msg: impl Into<String>) -> Self {
107        Self::InvalidVector(msg.into())
108    }
109
110    /// Create a serialization error.
111    pub fn serialization(msg: impl Into<String>) -> Self {
112        Self::SerializationError(msg.into())
113    }
114
115    /// Create a corrupted file error.
116    pub fn corrupted(msg: impl Into<String>) -> Self {
117        Self::CorruptedFile(msg.into())
118    }
119
120    /// Create a lock error.
121    pub fn lock(msg: impl Into<String>) -> Self {
122        Self::LockError(msg.into())
123    }
124
125    /// Create a graph error.
126    pub fn graph(msg: impl Into<String>) -> Self {
127        Self::GraphError(msg.into())
128    }
129
130    /// Create an invalid search params error.
131    pub fn invalid_search(msg: impl Into<String>) -> Self {
132        Self::InvalidSearchParams(msg.into())
133    }
134
135    /// Create an internal error.
136    pub fn internal(msg: impl Into<String>) -> Self {
137        Self::Internal(msg.into())
138    }
139
140    /// Check if this is a retriable error.
141    pub fn is_retriable(&self) -> bool {
142        matches!(
143            self,
144            Self::LockError(_) | Self::Timeout(_) | Self::ConcurrentModification
145        )
146    }
147
148    /// Check if this is a not-found error.
149    pub fn is_not_found(&self) -> bool {
150        matches!(self, Self::NotFound(_) | Self::FileNotFound(_))
151    }
152}
153
154impl From<bincode::Error> for VectorError {
155    fn from(e: bincode::Error) -> Self {
156        Self::SerializationError(e.to_string())
157    }
158}
159
160impl From<serde_json::Error> for VectorError {
161    fn from(e: serde_json::Error) -> Self {
162        Self::SerializationError(e.to_string())
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_error_messages() {
172        let err = VectorError::dimension_mismatch(1536, 768);
173        assert!(err.to_string().contains("1536"));
174        assert!(err.to_string().contains("768"));
175
176        let err = VectorError::NotFound(EmbeddingId::new());
177        assert!(err.to_string().contains("not found"));
178    }
179
180    #[test]
181    fn test_is_retriable() {
182        assert!(VectorError::lock("test").is_retriable());
183        assert!(VectorError::Timeout(1000).is_retriable());
184        assert!(!VectorError::EmptyIndex.is_retriable());
185    }
186
187    #[test]
188    fn test_is_not_found() {
189        assert!(VectorError::NotFound(EmbeddingId::new()).is_not_found());
190        assert!(VectorError::FileNotFound(PathBuf::from("/test")).is_not_found());
191        assert!(!VectorError::EmptyIndex.is_not_found());
192    }
193}