cvx_core/error/
mod.rs

1//! Error types for ChronosVector.
2//!
3//! Each subsystem defines its own error type. All convert into [`CvxError`]
4//! via `From` implementations, enabling uniform error handling at the API boundary.
5
6use thiserror::Error;
7
8/// Top-level error type wrapping all subsystem errors.
9///
10/// Library crates use their specific error types internally.
11/// At the API boundary, errors are converted to `CvxError` for uniform handling.
12#[derive(Debug, Error)]
13pub enum CvxError {
14    /// Error from the storage subsystem.
15    #[error(transparent)]
16    Storage(#[from] StorageError),
17
18    /// Error from the index subsystem.
19    #[error(transparent)]
20    Index(#[from] IndexError),
21
22    /// Error from the query engine.
23    #[error(transparent)]
24    Query(#[from] QueryError),
25
26    /// Error from the ingestion pipeline.
27    #[error(transparent)]
28    Ingest(#[from] IngestError),
29
30    /// Error from the analytics engine.
31    #[error(transparent)]
32    Analytics(#[from] AnalyticsError),
33
34    /// Error from the interpretability layer.
35    #[error(transparent)]
36    Explain(#[from] ExplainError),
37
38    /// Configuration error.
39    #[error("configuration error: {0}")]
40    Config(String),
41}
42
43/// Convenience alias for `Result<T, CvxError>`.
44pub type CvxResult<T> = Result<T, CvxError>;
45
46/// Storage subsystem errors.
47#[derive(Debug, Error)]
48pub enum StorageError {
49    /// Entity not found in the specified space.
50    #[error("entity {entity_id} not found in space {space_id}")]
51    EntityNotFound {
52        /// The requested entity ID.
53        entity_id: u64,
54        /// The embedding space ID.
55        space_id: u32,
56    },
57
58    /// Vector dimensionality does not match existing data.
59    #[error("dimension mismatch: expected {expected}, got {got}")]
60    DimensionMismatch {
61        /// Expected dimensionality.
62        expected: usize,
63        /// Actual dimensionality received.
64        got: usize,
65    },
66
67    /// Write-ahead log is corrupted.
68    #[error("WAL corrupted at offset {offset}")]
69    WalCorrupted {
70        /// Byte offset where corruption was detected.
71        offset: u64,
72    },
73
74    /// Tier migration failed.
75    #[error("tier migration failed: {reason}")]
76    TierMigration {
77        /// Description of the migration failure.
78        reason: String,
79    },
80
81    /// I/O error from the underlying storage engine.
82    #[error(transparent)]
83    Io(#[from] std::io::Error),
84}
85
86/// Index subsystem errors.
87#[derive(Debug, Error)]
88pub enum IndexError {
89    /// Referenced node does not exist in the graph.
90    #[error("node {0} not found in graph")]
91    NodeNotFound(u32),
92
93    /// Graph structure is inconsistent.
94    #[error("graph corrupted: {reason}")]
95    GraphCorrupted {
96        /// Description of the corruption.
97        reason: String,
98    },
99
100    /// Vector dimensionality does not match index configuration.
101    #[error("dimension mismatch: index expects {expected}, got {got}")]
102    DimensionMismatch {
103        /// Dimensionality configured for the index.
104        expected: usize,
105        /// Dimensionality of the provided vector.
106        got: usize,
107    },
108
109    /// Insertion into the index failed.
110    #[error("insert failed: {reason}")]
111    InsertFailed {
112        /// Description of the failure.
113        reason: String,
114    },
115}
116
117/// Query engine errors.
118#[derive(Debug, Error)]
119pub enum QueryError {
120    /// The query references an entity that does not exist.
121    #[error("entity {0} not found")]
122    EntityNotFound(u64),
123
124    /// Not enough historical data to perform the requested operation.
125    #[error("insufficient data: need {needed} points, have {have}")]
126    InsufficientData {
127        /// Minimum number of points required.
128        needed: usize,
129        /// Number of points available.
130        have: usize,
131    },
132
133    /// The query exceeded the configured timeout.
134    #[error("query timeout exceeded")]
135    Timeout,
136
137    /// The query planner could not produce a valid plan.
138    #[error("planning failed: {reason}")]
139    PlanningFailed {
140        /// Description of the planning failure.
141        reason: String,
142    },
143}
144
145/// Ingestion pipeline errors.
146#[derive(Debug, Error)]
147pub enum IngestError {
148    /// Input data failed validation checks.
149    #[error("validation failed: {reason}")]
150    ValidationFailed {
151        /// Description of the validation failure.
152        reason: String,
153    },
154
155    /// Vector dimensionality does not match the entity's existing data.
156    #[error("dimension mismatch for entity {entity_id}: expected {expected}, got {got}")]
157    DimensionMismatch {
158        /// The entity ID.
159        entity_id: u64,
160        /// Expected dimensionality.
161        expected: usize,
162        /// Actual dimensionality received.
163        got: usize,
164    },
165
166    /// The WAL is full and cannot accept more writes.
167    #[error("WAL full, backpressure active")]
168    WalFull,
169
170    /// Ingestion rate exceeded configured backpressure limits.
171    #[error("backpressure threshold exceeded")]
172    BackpressureExceeded,
173}
174
175/// Analytics engine errors.
176#[derive(Debug, Error)]
177pub enum AnalyticsError {
178    /// The ODE/SDE solver diverged during integration.
179    #[error("solver diverged at step {step}")]
180    SolverDiverged {
181        /// The integration step where divergence was detected.
182        step: usize,
183    },
184
185    /// Not enough data points to perform the requested analysis.
186    #[error("insufficient data: need {needed} points, have {have}")]
187    InsufficientData {
188        /// Minimum number of points required.
189        needed: usize,
190        /// Number of points available.
191        have: usize,
192    },
193
194    /// The required model has not been loaded or trained.
195    #[error("model not loaded: {name}")]
196    ModelNotLoaded {
197        /// Name of the model that was expected.
198        name: String,
199    },
200}
201
202/// Interpretability layer errors.
203#[derive(Debug, Error)]
204pub enum ExplainError {
205    /// The requested projection method is not available.
206    #[error("unsupported projection method: {0}")]
207    UnsupportedProjection(String),
208
209    /// Not enough data to produce the requested explanation.
210    #[error("insufficient data for explanation: {reason}")]
211    InsufficientData {
212        /// Description of what data is missing.
213        reason: String,
214    },
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn storage_error_converts_to_cvx_error() {
223        let err = StorageError::EntityNotFound {
224            entity_id: 42,
225            space_id: 0,
226        };
227        let cvx_err: CvxError = err.into();
228        assert!(matches!(cvx_err, CvxError::Storage(_)));
229        assert!(cvx_err.to_string().contains("entity 42"));
230    }
231
232    #[test]
233    fn index_error_converts_to_cvx_error() {
234        let err = IndexError::NodeNotFound(99);
235        let cvx_err: CvxError = err.into();
236        assert!(matches!(cvx_err, CvxError::Index(_)));
237    }
238
239    #[test]
240    fn query_error_displays_correctly() {
241        let err = QueryError::InsufficientData { needed: 5, have: 2 };
242        assert_eq!(err.to_string(), "insufficient data: need 5 points, have 2");
243    }
244
245    #[test]
246    fn all_error_types_are_send_sync() {
247        fn assert_send_sync<T: Send + Sync>() {}
248        assert_send_sync::<CvxError>();
249        assert_send_sync::<StorageError>();
250        assert_send_sync::<IndexError>();
251        assert_send_sync::<QueryError>();
252        assert_send_sync::<IngestError>();
253        assert_send_sync::<AnalyticsError>();
254        assert_send_sync::<ExplainError>();
255    }
256}