cellstate_core/
error.rs

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