1use crate::{EntityType, OperationalError};
4use std::time::Duration;
5use thiserror::Error;
6use uuid::Uuid;
7
8#[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#[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#[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#[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#[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#[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#[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
197pub type CellstateResult<T> = Result<T, CellstateError>;
199
200#[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}