cellstate_core/
error.rs

1//! Error types for CELLSTATE operations
2
3use crate::{EntityType, OperationalError};
4use std::time::Duration;
5use thiserror::Error;
6use uuid::Uuid;
7
8/// Storage layer errors.
9#[derive(Debug, Clone, Error, PartialEq, Eq)]
10pub enum StorageError {
11    #[error("Entity not found: {entity_type:?} with id {id}")]
12    NotFound { entity_type: EntityType, id: Uuid },
13
14    #[error("Insert failed for {entity_type:?}: {reason}")]
15    InsertFailed {
16        entity_type: EntityType,
17        reason: String,
18    },
19
20    #[error("Update failed for {entity_type:?} with id {id}: {reason}")]
21    UpdateFailed {
22        entity_type: EntityType,
23        id: Uuid,
24        reason: String,
25    },
26
27    #[error("Transaction failed: {reason}")]
28    TransactionFailed { reason: String },
29
30    #[error("Index error on {index_name}: {reason}")]
31    IndexError { index_name: String, reason: String },
32
33    #[error("Storage lock poisoned")]
34    LockPoisoned,
35
36    #[error("SPI error: {reason}")]
37    SpiError { reason: String },
38
39    #[error("Cache invalidation failed: {reason}")]
40    CacheInvalidationFailed { reason: String },
41}
42
43/// LLM provider errors.
44#[derive(Debug, Clone, Error, PartialEq, Eq)]
45pub enum LlmError {
46    #[error("No LLM provider configured")]
47    ProviderNotConfigured,
48
49    #[error("Request to {provider} failed with status {status}: {message}")]
50    RequestFailed {
51        provider: String,
52        status: i32,
53        message: String,
54    },
55
56    #[error("Rate limited by {provider}, retry after {retry_after_ms}ms")]
57    RateLimited {
58        provider: String,
59        retry_after_ms: i64,
60    },
61
62    #[error("Invalid response from {provider}: {reason}")]
63    InvalidResponse { provider: String, reason: String },
64
65    #[error("Embedding failed: {reason}")]
66    EmbeddingFailed { reason: String },
67
68    #[error("Summarization failed: {reason}")]
69    SummarizationFailed { reason: String },
70
71    #[error("Session error from {provider}: {reason}")]
72    SessionError { provider: String, reason: String },
73}
74
75/// Validation errors.
76#[derive(Debug, Clone, Error, PartialEq, Eq)]
77pub enum ValidationError {
78    #[error("Required field missing: {field}")]
79    RequiredFieldMissing { field: String },
80
81    #[error("Invalid value for {field}: {reason}")]
82    InvalidValue { field: String, reason: String },
83
84    #[error("Constraint violation on {constraint}: {reason}")]
85    ConstraintViolation { constraint: String, reason: String },
86
87    #[error("Circular reference detected in {entity_type:?}: {ids:?}")]
88    CircularReference {
89        entity_type: EntityType,
90        ids: Vec<Uuid>,
91    },
92
93    #[error("Stale data for {entity_type:?} with id {id}, age: {age:?}")]
94    StaleData {
95        entity_type: EntityType,
96        id: Uuid,
97        age: Duration,
98    },
99
100    #[error("Contradiction detected between artifacts {artifact_a} and {artifact_b}")]
101    Contradiction { artifact_a: Uuid, artifact_b: Uuid },
102
103    #[error("Invalid replay data for {entity}: {reason}")]
104    InvalidReplayData { entity: String, reason: String },
105}
106
107/// Configuration errors.
108#[derive(Debug, Clone, Error, PartialEq, Eq)]
109pub enum ConfigError {
110    #[error("Missing required configuration field: {field}")]
111    MissingRequired { field: String },
112
113    #[error("Invalid value for {field}: {value} - {reason}")]
114    InvalidValue {
115        field: String,
116        value: String,
117        reason: String,
118    },
119
120    #[error("Incompatible options: {option_a} and {option_b}")]
121    IncompatibleOptions { option_a: String, option_b: String },
122
123    #[error("Provider not supported: {provider}")]
124    ProviderNotSupported { provider: String },
125}
126
127/// Vector operation errors.
128#[derive(Debug, Clone, Error, PartialEq, Eq)]
129pub enum VectorError {
130    #[error("Dimension mismatch: expected {expected}, got {got}")]
131    DimensionMismatch { expected: i32, got: i32 },
132
133    #[error("Invalid vector: {reason}")]
134    InvalidVector { reason: String },
135
136    #[error("Model mismatch: expected {expected}, got {got}")]
137    ModelMismatch { expected: String, got: String },
138
139    #[error("Vector contains non-finite values (NaN or Infinity)")]
140    NonFiniteValues,
141}
142
143/// Agent coordination errors.
144#[derive(Debug, Clone, Error, PartialEq, Eq)]
145pub enum AgentError {
146    #[error("Agent not registered: {agent_id}")]
147    NotRegistered { agent_id: Uuid },
148
149    #[error("Lock acquisition failed for {resource}: held by {holder}")]
150    LockAcquisitionFailed { resource: String, holder: Uuid },
151
152    #[error("Lock expired: {lock_id}")]
153    LockExpired { lock_id: Uuid },
154
155    #[error("Message delivery failed for {message_id}: {reason}")]
156    MessageDeliveryFailed { message_id: Uuid, reason: String },
157
158    #[error("Delegation failed: {reason}")]
159    DelegationFailed { reason: String },
160
161    #[error("Handoff failed: {reason}")]
162    HandoffFailed { reason: String },
163
164    #[error("Permission denied for agent {agent_id}: {action} on {resource}")]
165    PermissionDenied {
166        agent_id: Uuid,
167        action: String,
168        resource: String,
169    },
170}
171
172/// Master error type for all CELLSTATE errors.
173#[derive(Debug, Clone, Error)]
174pub enum CellstateError {
175    #[error("Storage error: {0}")]
176    Storage(#[from] StorageError),
177
178    #[error("LLM error: {0}")]
179    Llm(#[from] LlmError),
180
181    #[error("Validation error: {0}")]
182    Validation(#[from] ValidationError),
183
184    #[error("Config error: {0}")]
185    Config(#[from] ConfigError),
186
187    #[error("Vector error: {0}")]
188    Vector(#[from] VectorError),
189
190    #[error("Agent error: {0}")]
191    Agent(#[from] AgentError),
192
193    #[error("Operational error: {0}")]
194    Operational(#[from] OperationalError),
195}
196
197/// Result type alias for CELLSTATE operations.
198pub type CellstateResult<T> = Result<T, CellstateError>;
199
200// =============================================================================
201// TESTS
202// =============================================================================
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_storage_error_display_not_found() {
210        let err = StorageError::NotFound {
211            entity_type: EntityType::Trajectory,
212            id: Uuid::nil(),
213        };
214        let msg = format!("{}", err);
215        assert!(msg.contains("Entity not found"));
216        assert!(msg.contains("Trajectory"));
217        assert!(msg.contains("00000000-0000-0000-0000-000000000000"));
218    }
219
220    #[test]
221    fn test_validation_error_display_stale_data() {
222        let err = ValidationError::StaleData {
223            entity_type: EntityType::Artifact,
224            id: Uuid::nil(),
225            age: Duration::from_secs(42),
226        };
227        let msg = format!("{}", err);
228        assert!(msg.contains("Stale data"));
229        assert!(msg.contains("Artifact"));
230    }
231
232    #[test]
233    fn test_llm_error_display_rate_limited() {
234        let err = LlmError::RateLimited {
235            provider: "openai".to_string(),
236            retry_after_ms: 1500,
237        };
238        let msg = format!("{}", err);
239        assert!(msg.contains("Rate limited"));
240        assert!(msg.contains("openai"));
241        assert!(msg.contains("1500"));
242    }
243
244    #[test]
245    fn test_config_error_display_invalid_value() {
246        let err = ConfigError::InvalidValue {
247            field: "api_base_url".to_string(),
248            value: "bad".to_string(),
249            reason: "must be url".to_string(),
250        };
251        let msg = format!("{}", err);
252        assert!(msg.contains("api_base_url"));
253        assert!(msg.contains("bad"));
254        assert!(msg.contains("must be url"));
255    }
256
257    #[test]
258    fn test_vector_error_display_dimension_mismatch() {
259        let err = VectorError::DimensionMismatch {
260            expected: 1536,
261            got: 768,
262        };
263        let msg = format!("{}", err);
264        assert!(msg.contains("Dimension mismatch"));
265        assert!(msg.contains("1536"));
266        assert!(msg.contains("768"));
267    }
268
269    #[test]
270    fn test_agent_error_display_permission_denied() {
271        let err = AgentError::PermissionDenied {
272            agent_id: Uuid::nil(),
273            action: "read".to_string(),
274            resource: "artifact".to_string(),
275        };
276        let msg = format!("{}", err);
277        assert!(msg.contains("Permission denied"));
278        assert!(msg.contains("read"));
279        assert!(msg.contains("artifact"));
280    }
281
282    #[test]
283    fn test_cellstate_error_from_variants() {
284        let storage = CellstateError::from(StorageError::LockPoisoned);
285        assert!(matches!(storage, CellstateError::Storage(_)));
286
287        let llm = CellstateError::from(LlmError::ProviderNotConfigured);
288        assert!(matches!(llm, CellstateError::Llm(_)));
289
290        let validation = CellstateError::from(ValidationError::RequiredFieldMissing {
291            field: "name".to_string(),
292        });
293        assert!(matches!(validation, CellstateError::Validation(_)));
294
295        let config = CellstateError::from(ConfigError::ProviderNotSupported {
296            provider: "test".to_string(),
297        });
298        assert!(matches!(config, CellstateError::Config(_)));
299
300        let vector = CellstateError::from(VectorError::InvalidVector {
301            reason: "empty".to_string(),
302        });
303        assert!(matches!(vector, CellstateError::Vector(_)));
304
305        let agent = CellstateError::from(AgentError::DelegationFailed {
306            reason: "timeout".to_string(),
307        });
308        assert!(matches!(agent, CellstateError::Agent(_)));
309
310        let operational = CellstateError::from(OperationalError::SerializationError {
311            message: "broken payload".to_string(),
312        });
313        assert!(matches!(operational, CellstateError::Operational(_)));
314    }
315
316    #[test]
317    fn test_storage_error_display_lock_poisoned() {
318        let err = StorageError::LockPoisoned;
319        let msg = format!("{}", err);
320        assert!(msg.contains("Storage lock poisoned"));
321    }
322
323    #[test]
324    fn test_validation_error_display_invalid_field_value() {
325        let err = ValidationError::InvalidValue {
326            field: "priority".to_string(),
327            reason: "must be numeric".to_string(),
328        };
329        let msg = format!("{}", err);
330        assert!(msg.contains("priority"));
331        assert!(msg.contains("must be numeric"));
332    }
333
334    #[test]
335    fn test_agent_error_display_delegation_failed() {
336        let err = AgentError::DelegationFailed {
337            reason: "timeout".to_string(),
338        };
339        let msg = format!("{}", err);
340        assert!(msg.contains("Delegation failed"));
341        assert!(msg.contains("timeout"));
342    }
343}