1use crate::EntityType;
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
40#[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#[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#[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#[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#[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#[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
188pub type CellstateResult<T> = Result<T, CellstateError>;
190
191#[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}