1#[derive(Debug, thiserror::Error)]
3pub enum AllSourceError {
4 #[error("Event not found: {0}")]
5 EventNotFound(String),
6
7 #[error("Entity not found: {0}")]
8 EntityNotFound(String),
9
10 #[error("Tenant already exists: {0}")]
11 TenantAlreadyExists(String),
12
13 #[error("Tenant not found: {0}")]
14 TenantNotFound(String),
15
16 #[error("Invalid event: {0}")]
17 InvalidEvent(String),
18
19 #[error("Invalid query: {0}")]
20 InvalidQuery(String),
21
22 #[error("Invalid input: {0}")]
23 InvalidInput(String),
24
25 #[error("Storage error: {0}")]
26 StorageError(String),
27
28 #[error("Serialization error: {0}")]
29 SerializationError(#[from] serde_json::Error),
30
31 #[error("Arrow error: {0}")]
32 ArrowError(String),
33
34 #[error("Index error: {0}")]
35 IndexError(String),
36
37 #[error("Validation error: {0}")]
38 ValidationError(String),
39
40 #[error("Concurrency error: {0}")]
41 ConcurrencyError(String),
42
43 #[error("Version conflict: expected {expected}, current {current}")]
44 VersionConflict { expected: u64, current: u64 },
45
46 #[error("Queue full: {0}")]
47 QueueFull(String),
48
49 #[error("Internal error: {0}")]
50 InternalError(String),
51
52 #[error(
56 "Schema violation for event_type '{event_type}' (schema v{schema_version}): {errors:?}"
57 )]
58 SchemaViolation {
59 event_type: String,
60 schema_version: u32,
61 errors: Vec<String>,
62 },
63}
64
65impl AllSourceError {
66 pub fn is_retryable(&self) -> bool {
69 matches!(
70 self,
71 AllSourceError::StorageError(_)
72 | AllSourceError::ConcurrencyError(_)
73 | AllSourceError::VersionConflict { .. }
74 | AllSourceError::QueueFull(_)
75 )
76 }
77}
78
79pub use AllSourceError as Error;
81
82impl From<arrow::error::ArrowError> for AllSourceError {
83 fn from(err: arrow::error::ArrowError) -> Self {
84 AllSourceError::ArrowError(err.to_string())
85 }
86}
87
88impl From<parquet::errors::ParquetError> for AllSourceError {
89 fn from(err: parquet::errors::ParquetError) -> Self {
90 AllSourceError::StorageError(err.to_string())
91 }
92}
93
94impl From<crate::infrastructure::persistence::SimdJsonError> for AllSourceError {
95 fn from(err: crate::infrastructure::persistence::SimdJsonError) -> Self {
96 AllSourceError::SerializationError(serde_json::Error::io(std::io::Error::new(
97 std::io::ErrorKind::InvalidData,
98 err.to_string(),
99 )))
100 }
101}
102
103#[cfg(feature = "postgres")]
104impl From<sqlx::Error> for AllSourceError {
105 fn from(err: sqlx::Error) -> Self {
106 AllSourceError::StorageError(format!("Database error: {err}"))
107 }
108}
109
110pub type Result<T> = std::result::Result<T, AllSourceError>;
112
113#[cfg(feature = "server")]
114mod axum_impl {
115 use super::AllSourceError;
116 use axum::{
117 http::StatusCode,
118 response::{IntoResponse, Response},
119 };
120
121 impl IntoResponse for AllSourceError {
123 fn into_response(self) -> Response {
124 let (status, error_message) = match self {
125 AllSourceError::EventNotFound(_)
126 | AllSourceError::EntityNotFound(_)
127 | AllSourceError::TenantNotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
128 AllSourceError::InvalidEvent(_)
129 | AllSourceError::InvalidQuery(_)
130 | AllSourceError::InvalidInput(_)
131 | AllSourceError::ValidationError(_) => (StatusCode::BAD_REQUEST, self.to_string()),
132 AllSourceError::VersionConflict { expected, current } => {
133 let body = serde_json::json!({
134 "error": "version_conflict",
135 "expected_version": expected,
136 "current_version": current,
137 });
138 return (StatusCode::CONFLICT, axum::Json(body)).into_response();
139 }
140 AllSourceError::TenantAlreadyExists(_) | AllSourceError::ConcurrencyError(_) => {
141 (StatusCode::CONFLICT, self.to_string())
142 }
143 AllSourceError::QueueFull(_) => (StatusCode::SERVICE_UNAVAILABLE, self.to_string()),
144 AllSourceError::StorageError(_)
145 | AllSourceError::ArrowError(_)
146 | AllSourceError::IndexError(_)
147 | AllSourceError::InternalError(_) => {
148 (StatusCode::INTERNAL_SERVER_ERROR, self.to_string())
149 }
150 AllSourceError::SerializationError(_) => {
151 (StatusCode::UNPROCESSABLE_ENTITY, self.to_string())
152 }
153 AllSourceError::SchemaViolation {
154 event_type,
155 schema_version,
156 errors,
157 } => {
158 let body = serde_json::json!({
159 "error": {
160 "code": "schema_violation",
161 "message": format!(
162 "Event payload does not match registered schema for '{event_type}'"
163 ),
164 "details": {
165 "event_type": event_type,
166 "schema_version": schema_version,
167 "errors": errors,
168 }
169 }
170 });
171 return (StatusCode::UNPROCESSABLE_ENTITY, axum::Json(body)).into_response();
172 }
173 };
174
175 let body = serde_json::json!({
176 "error": error_message,
177 });
178
179 (status, axum::Json(body)).into_response()
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[cfg(feature = "server")]
189 use axum::{http::StatusCode, response::IntoResponse};
190
191 #[test]
192 fn test_error_display() {
193 let err = AllSourceError::EventNotFound("event-123".to_string());
194 assert_eq!(err.to_string(), "Event not found: event-123");
195
196 let err = AllSourceError::EntityNotFound("entity-456".to_string());
197 assert_eq!(err.to_string(), "Entity not found: entity-456");
198
199 let err = AllSourceError::TenantAlreadyExists("tenant-1".to_string());
200 assert_eq!(err.to_string(), "Tenant already exists: tenant-1");
201
202 let err = AllSourceError::TenantNotFound("tenant-2".to_string());
203 assert_eq!(err.to_string(), "Tenant not found: tenant-2");
204 }
205
206 #[test]
207 fn test_error_variants() {
208 let errors: Vec<AllSourceError> = vec![
209 AllSourceError::InvalidEvent("bad event".to_string()),
210 AllSourceError::InvalidQuery("bad query".to_string()),
211 AllSourceError::InvalidInput("bad input".to_string()),
212 AllSourceError::StorageError("storage failed".to_string()),
213 AllSourceError::ArrowError("arrow failed".to_string()),
214 AllSourceError::IndexError("index failed".to_string()),
215 AllSourceError::ValidationError("validation failed".to_string()),
216 AllSourceError::ConcurrencyError("conflict".to_string()),
217 AllSourceError::QueueFull("queue full".to_string()),
218 AllSourceError::InternalError("internal error".to_string()),
219 ];
220
221 for err in errors {
222 let msg = err.to_string();
223 assert!(!msg.is_empty());
224 }
225 }
226
227 #[cfg(feature = "server")]
228 #[test]
229 fn test_into_response_not_found() {
230 let err = AllSourceError::EventNotFound("event-123".to_string());
231 let response = err.into_response();
232 assert_eq!(response.status(), StatusCode::NOT_FOUND);
233
234 let err = AllSourceError::EntityNotFound("entity-456".to_string());
235 let response = err.into_response();
236 assert_eq!(response.status(), StatusCode::NOT_FOUND);
237
238 let err = AllSourceError::TenantNotFound("tenant-1".to_string());
239 let response = err.into_response();
240 assert_eq!(response.status(), StatusCode::NOT_FOUND);
241 }
242
243 #[cfg(feature = "server")]
244 #[test]
245 fn test_into_response_bad_request() {
246 let err = AllSourceError::InvalidEvent("bad event".to_string());
247 let response = err.into_response();
248 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
249
250 let err = AllSourceError::InvalidQuery("bad query".to_string());
251 let response = err.into_response();
252 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
253
254 let err = AllSourceError::InvalidInput("bad input".to_string());
255 let response = err.into_response();
256 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
257
258 let err = AllSourceError::ValidationError("validation failed".to_string());
259 let response = err.into_response();
260 assert_eq!(response.status(), StatusCode::BAD_REQUEST);
261 }
262
263 #[cfg(feature = "server")]
264 #[test]
265 fn test_into_response_conflict() {
266 let err = AllSourceError::TenantAlreadyExists("tenant-1".to_string());
267 let response = err.into_response();
268 assert_eq!(response.status(), StatusCode::CONFLICT);
269
270 let err = AllSourceError::ConcurrencyError("conflict".to_string());
271 let response = err.into_response();
272 assert_eq!(response.status(), StatusCode::CONFLICT);
273 }
274
275 #[cfg(feature = "server")]
276 #[test]
277 fn test_into_response_service_unavailable() {
278 let err = AllSourceError::QueueFull("queue is full".to_string());
279 let response = err.into_response();
280 assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
281 }
282
283 #[cfg(feature = "server")]
287 #[tokio::test]
288 async fn test_into_response_schema_violation() {
289 use axum::body::to_bytes;
290 let err = AllSourceError::SchemaViolation {
291 event_type: "user.created".to_string(),
292 schema_version: 2,
293 errors: vec!["Missing required field: email".to_string()],
294 };
295 let response = err.into_response();
296 assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
297
298 let body_bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
299 let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
300 assert_eq!(body["error"]["code"], "schema_violation");
301 assert_eq!(body["error"]["details"]["event_type"], "user.created");
302 assert_eq!(body["error"]["details"]["schema_version"], 2);
303 let errors = body["error"]["details"]["errors"].as_array().unwrap();
304 assert_eq!(errors.len(), 1);
305 assert!(errors[0].as_str().unwrap().contains("email"));
306 }
307
308 #[cfg(feature = "server")]
309 #[test]
310 fn test_into_response_internal_error() {
311 let err = AllSourceError::StorageError("storage error".to_string());
312 let response = err.into_response();
313 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
314
315 let err = AllSourceError::ArrowError("arrow error".to_string());
316 let response = err.into_response();
317 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
318
319 let err = AllSourceError::IndexError("index error".to_string());
320 let response = err.into_response();
321 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
322
323 let err = AllSourceError::InternalError("internal error".to_string());
324 let response = err.into_response();
325 assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
326 }
327
328 #[test]
329 fn test_from_arrow_error() {
330 let arrow_err = arrow::error::ArrowError::InvalidArgumentError("test".to_string());
331 let err: AllSourceError = arrow_err.into();
332 assert!(matches!(err, AllSourceError::ArrowError(_)));
333 }
334
335 #[test]
336 fn test_from_parquet_error() {
337 let parquet_err = parquet::errors::ParquetError::General("test".to_string());
338 let err: AllSourceError = parquet_err.into();
339 assert!(matches!(err, AllSourceError::StorageError(_)));
340 }
341
342 #[test]
343 fn test_error_debug() {
344 let err = AllSourceError::EventNotFound("test".to_string());
345 let debug_str = format!("{err:?}");
346 assert!(debug_str.contains("EventNotFound"));
347 }
348
349 #[test]
350 fn test_is_retryable() {
351 assert!(AllSourceError::StorageError("io".to_string()).is_retryable());
353 assert!(AllSourceError::ConcurrencyError("conflict".to_string()).is_retryable());
354 assert!(AllSourceError::QueueFull("backpressure".to_string()).is_retryable());
355
356 assert!(!AllSourceError::EventNotFound("e1".to_string()).is_retryable());
358 assert!(!AllSourceError::InvalidEvent("bad".to_string()).is_retryable());
359 assert!(!AllSourceError::InvalidQuery("bad".to_string()).is_retryable());
360 assert!(!AllSourceError::ValidationError("bad".to_string()).is_retryable());
361 assert!(!AllSourceError::TenantNotFound("t1".to_string()).is_retryable());
362 assert!(!AllSourceError::TenantAlreadyExists("t1".to_string()).is_retryable());
363 assert!(!AllSourceError::InternalError("bug".to_string()).is_retryable());
364 }
365}