Skip to main content

moloch_core/
error.rs

1//! Error types for Moloch.
2//!
3//! Provides structured errors with:
4//! - Unique error codes for API responses
5//! - Source error chaining
6//! - Client vs server error categorization
7
8use std::io;
9use thiserror::Error;
10
11/// Result type for Moloch operations.
12pub type Result<T> = std::result::Result<T, Error>;
13
14/// Error codes for API responses.
15///
16/// Codes are structured as:
17/// - 1xxx: Validation errors (client)
18/// - 2xxx: Not found errors (client)
19/// - 3xxx: Conflict errors (client)
20/// - 4xxx: Authentication/authorization (client)
21/// - 5xxx: Storage errors (server)
22/// - 6xxx: Internal errors (server)
23/// - 7xxx: Network errors (server)
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[repr(u16)]
26pub enum ErrorCode {
27    // Validation errors (1xxx)
28    InvalidHash = 1001,
29    InvalidKey = 1002,
30    InvalidSignature = 1003,
31    InvalidEvent = 1004,
32    InvalidBlock = 1005,
33    InvalidProof = 1006,
34    InvalidTimestamp = 1007,
35    InvalidFormat = 1008,
36    InvalidInput = 1009,
37
38    // Not found errors (2xxx)
39    EventNotFound = 2001,
40    BlockNotFound = 2002,
41    NodeNotFound = 2003,
42    PeakNotFound = 2004,
43
44    // Conflict errors (3xxx)
45    DuplicateEvent = 3001,
46    DuplicateBlock = 3002,
47    ChainFork = 3003,
48
49    // Auth errors (4xxx)
50    Unauthorized = 4001,
51    Forbidden = 4002,
52
53    // Storage errors (5xxx)
54    StorageRead = 5001,
55    StorageWrite = 5002,
56    StorageCorruption = 5003,
57    StorageInit = 5004,
58
59    // Internal errors (6xxx)
60    Serialization = 6001,
61    Deserialization = 6002,
62    Internal = 6003,
63
64    // Network errors (7xxx)
65    ConnectionFailed = 7001,
66    Timeout = 7002,
67    ProtocolError = 7003,
68}
69
70impl ErrorCode {
71    /// Get the numeric code.
72    pub fn code(self) -> u16 {
73        self as u16
74    }
75
76    /// Check if this is a client error (4xx equivalent).
77    pub fn is_client_error(self) -> bool {
78        (1000..5000).contains(&self.code())
79    }
80
81    /// Check if this is a server error (5xx equivalent).
82    pub fn is_server_error(self) -> bool {
83        self.code() >= 5000
84    }
85
86    /// Check if this error is retryable.
87    pub fn is_retryable(self) -> bool {
88        matches!(
89            self,
90            ErrorCode::StorageRead
91                | ErrorCode::StorageWrite
92                | ErrorCode::ConnectionFailed
93                | ErrorCode::Timeout
94        )
95    }
96}
97
98impl std::fmt::Display for ErrorCode {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        write!(f, "E{:04}", self.code())
101    }
102}
103
104/// Errors that can occur in Moloch.
105#[derive(Debug, Error)]
106pub enum Error {
107    // ========================================================================
108    // Validation Errors (client errors)
109    // ========================================================================
110    /// Invalid input (generic validation failure).
111    #[error("[{code}] invalid input: {message}")]
112    InvalidInput { code: ErrorCode, message: String },
113
114    /// Invalid hash format or value.
115    #[error("[{code}] invalid hash: {message}")]
116    InvalidHash {
117        code: ErrorCode,
118        message: String,
119        #[source]
120        source: Option<Box<dyn std::error::Error + Send + Sync>>,
121    },
122
123    /// Invalid cryptographic key.
124    #[error("[{code}] invalid key: {message}")]
125    InvalidKey {
126        code: ErrorCode,
127        message: String,
128        #[source]
129        source: Option<Box<dyn std::error::Error + Send + Sync>>,
130    },
131
132    /// Signature verification failed.
133    #[error("[{code}] signature verification failed")]
134    InvalidSignature { code: ErrorCode },
135
136    /// Event validation failed.
137    #[error("[{code}] invalid event: {message}")]
138    InvalidEvent { code: ErrorCode, message: String },
139
140    /// Block validation failed.
141    #[error("[{code}] invalid block: {message}")]
142    InvalidBlock { code: ErrorCode, message: String },
143
144    /// Proof verification failed.
145    #[error("[{code}] invalid proof: {message}")]
146    InvalidProof { code: ErrorCode, message: String },
147
148    // ========================================================================
149    // Not Found Errors (client errors)
150    // ========================================================================
151    /// Item not found.
152    #[error("[{code}] not found: {message}")]
153    NotFound { code: ErrorCode, message: String },
154
155    // ========================================================================
156    // Conflict Errors (client errors)
157    // ========================================================================
158    /// Duplicate item.
159    #[error("[{code}] duplicate: {message}")]
160    Duplicate { code: ErrorCode, message: String },
161
162    // ========================================================================
163    // Storage Errors (server errors)
164    // ========================================================================
165    /// Storage operation failed.
166    #[error("[{code}] storage error: {message}")]
167    Storage {
168        code: ErrorCode,
169        message: String,
170        #[source]
171        source: Option<Box<dyn std::error::Error + Send + Sync>>,
172    },
173
174    // ========================================================================
175    // Serialization Errors (server errors)
176    // ========================================================================
177    /// Serialization/deserialization failed.
178    #[error("[{code}] serialization error: {message}")]
179    Serialization {
180        code: ErrorCode,
181        message: String,
182        #[source]
183        source: Option<Box<dyn std::error::Error + Send + Sync>>,
184    },
185
186    // ========================================================================
187    // Internal Errors (server errors)
188    // ========================================================================
189    /// Internal error.
190    #[error("[{code}] internal error: {message}")]
191    Internal { code: ErrorCode, message: String },
192}
193
194impl Error {
195    /// Get the error code.
196    pub fn code(&self) -> ErrorCode {
197        match self {
198            Error::InvalidInput { code, .. } => *code,
199            Error::InvalidHash { code, .. } => *code,
200            Error::InvalidKey { code, .. } => *code,
201            Error::InvalidSignature { code } => *code,
202            Error::InvalidEvent { code, .. } => *code,
203            Error::InvalidBlock { code, .. } => *code,
204            Error::InvalidProof { code, .. } => *code,
205            Error::NotFound { code, .. } => *code,
206            Error::Duplicate { code, .. } => *code,
207            Error::Storage { code, .. } => *code,
208            Error::Serialization { code, .. } => *code,
209            Error::Internal { code, .. } => *code,
210        }
211    }
212
213    /// Check if this is a client error.
214    pub fn is_client_error(&self) -> bool {
215        self.code().is_client_error()
216    }
217
218    /// Check if this is a server error.
219    pub fn is_server_error(&self) -> bool {
220        self.code().is_server_error()
221    }
222
223    /// Check if this error is retryable.
224    pub fn is_retryable(&self) -> bool {
225        self.code().is_retryable()
226    }
227}
228
229// ============================================================================
230// Convenience constructors (backward compatible)
231// ============================================================================
232
233impl Error {
234    /// Create an InvalidInput error (generic validation failure).
235    pub fn invalid_input(message: impl Into<String>) -> Self {
236        Error::InvalidInput {
237            code: ErrorCode::InvalidInput,
238            message: message.into(),
239        }
240    }
241
242    /// Create an InvalidHash error.
243    pub fn invalid_hash(message: impl Into<String>) -> Self {
244        Error::InvalidHash {
245            code: ErrorCode::InvalidHash,
246            message: message.into(),
247            source: None,
248        }
249    }
250
251    /// Create an InvalidKey error.
252    pub fn invalid_key(message: impl Into<String>) -> Self {
253        Error::InvalidKey {
254            code: ErrorCode::InvalidKey,
255            message: message.into(),
256            source: None,
257        }
258    }
259
260    /// Create an InvalidSignature error.
261    pub fn invalid_signature() -> Self {
262        Error::InvalidSignature {
263            code: ErrorCode::InvalidSignature,
264        }
265    }
266
267    /// Create an InvalidEvent error.
268    pub fn invalid_event(message: impl Into<String>) -> Self {
269        Error::InvalidEvent {
270            code: ErrorCode::InvalidEvent,
271            message: message.into(),
272        }
273    }
274
275    /// Create an InvalidBlock error.
276    pub fn invalid_block(message: impl Into<String>) -> Self {
277        Error::InvalidBlock {
278            code: ErrorCode::InvalidBlock,
279            message: message.into(),
280        }
281    }
282
283    /// Create an InvalidProof error.
284    pub fn invalid_proof(message: impl Into<String>) -> Self {
285        Error::InvalidProof {
286            code: ErrorCode::InvalidProof,
287            message: message.into(),
288        }
289    }
290
291    /// Create a NotFound error for events.
292    pub fn event_not_found(message: impl Into<String>) -> Self {
293        Error::NotFound {
294            code: ErrorCode::EventNotFound,
295            message: message.into(),
296        }
297    }
298
299    /// Create a NotFound error for blocks.
300    pub fn block_not_found(message: impl Into<String>) -> Self {
301        Error::NotFound {
302            code: ErrorCode::BlockNotFound,
303            message: message.into(),
304        }
305    }
306
307    /// Create a NotFound error (generic).
308    pub fn not_found(message: impl Into<String>) -> Self {
309        Error::NotFound {
310            code: ErrorCode::NodeNotFound,
311            message: message.into(),
312        }
313    }
314
315    /// Create a Duplicate error.
316    pub fn duplicate(message: impl Into<String>) -> Self {
317        Error::Duplicate {
318            code: ErrorCode::DuplicateEvent,
319            message: message.into(),
320        }
321    }
322
323    /// Create a Storage error.
324    pub fn storage(message: impl Into<String>) -> Self {
325        Error::Storage {
326            code: ErrorCode::StorageRead,
327            message: message.into(),
328            source: None,
329        }
330    }
331
332    /// Create an Internal error.
333    pub fn internal(message: impl Into<String>) -> Self {
334        Error::Internal {
335            code: ErrorCode::Internal,
336            message: message.into(),
337        }
338    }
339}
340
341// ============================================================================
342// From implementations for automatic conversion
343// ============================================================================
344
345impl From<bincode::Error> for Error {
346    fn from(e: bincode::Error) -> Self {
347        Error::Serialization {
348            code: ErrorCode::Serialization,
349            message: e.to_string(),
350            source: Some(Box::new(e)),
351        }
352    }
353}
354
355impl From<serde_json::Error> for Error {
356    fn from(e: serde_json::Error) -> Self {
357        Error::Serialization {
358            code: ErrorCode::Serialization,
359            message: e.to_string(),
360            source: Some(Box::new(e)),
361        }
362    }
363}
364
365impl From<io::Error> for Error {
366    fn from(e: io::Error) -> Self {
367        Error::Storage {
368            code: ErrorCode::StorageRead,
369            message: e.to_string(),
370            source: Some(Box::new(e)),
371        }
372    }
373}
374
375impl From<hex::FromHexError> for Error {
376    fn from(e: hex::FromHexError) -> Self {
377        Error::InvalidHash {
378            code: ErrorCode::InvalidHash,
379            message: e.to_string(),
380            source: Some(Box::new(e)),
381        }
382    }
383}
384
385// ============================================================================
386// Legacy constructors for backward compatibility
387// ============================================================================
388
389// These allow the old pattern: Error::InvalidHash("message".into())
390// to work via From<String> for the message field
391
392/// Helper to maintain backward compatibility with Error::Variant(String) pattern.
393#[allow(unused_macros)]
394macro_rules! impl_from_string {
395    ($variant:ident, $code:expr) => {
396        impl From<&str> for $variant {
397            fn from(s: &str) -> Self {
398                $variant(s.to_string())
399            }
400        }
401    };
402}
403
404// For backward compatibility, we provide a way to construct errors from strings
405// using the old pattern. This is a transitional measure.
406
407#[doc(hidden)]
408pub struct InvalidHashCompat(pub String);
409#[doc(hidden)]
410pub struct InvalidKeyCompat(pub String);
411#[doc(hidden)]
412pub struct InvalidEventCompat(pub String);
413#[doc(hidden)]
414pub struct InvalidBlockCompat(pub String);
415#[doc(hidden)]
416pub struct InvalidProofCompat(pub String);
417#[doc(hidden)]
418pub struct StorageCompat(pub String);
419#[doc(hidden)]
420pub struct NotFoundCompat(pub String);
421#[doc(hidden)]
422pub struct DuplicateCompat(pub String);
423#[doc(hidden)]
424pub struct SerializationCompat(pub String);
425#[doc(hidden)]
426pub struct InternalCompat(pub String);
427
428impl From<InvalidHashCompat> for Error {
429    fn from(c: InvalidHashCompat) -> Self {
430        Error::invalid_hash(c.0)
431    }
432}
433
434impl From<InvalidKeyCompat> for Error {
435    fn from(c: InvalidKeyCompat) -> Self {
436        Error::invalid_key(c.0)
437    }
438}
439
440impl From<InvalidEventCompat> for Error {
441    fn from(c: InvalidEventCompat) -> Self {
442        Error::invalid_event(c.0)
443    }
444}
445
446impl From<InvalidBlockCompat> for Error {
447    fn from(c: InvalidBlockCompat) -> Self {
448        Error::invalid_block(c.0)
449    }
450}
451
452impl From<InvalidProofCompat> for Error {
453    fn from(c: InvalidProofCompat) -> Self {
454        Error::invalid_proof(c.0)
455    }
456}
457
458impl From<StorageCompat> for Error {
459    fn from(c: StorageCompat) -> Self {
460        Error::storage(c.0)
461    }
462}
463
464impl From<NotFoundCompat> for Error {
465    fn from(c: NotFoundCompat) -> Self {
466        Error::not_found(c.0)
467    }
468}
469
470impl From<DuplicateCompat> for Error {
471    fn from(c: DuplicateCompat) -> Self {
472        Error::duplicate(c.0)
473    }
474}
475
476impl From<SerializationCompat> for Error {
477    fn from(c: SerializationCompat) -> Self {
478        Error::Serialization {
479            code: ErrorCode::Serialization,
480            message: c.0,
481            source: None,
482        }
483    }
484}
485
486impl From<InternalCompat> for Error {
487    fn from(c: InternalCompat) -> Self {
488        Error::internal(c.0)
489    }
490}
491
492#[cfg(test)]
493mod tests {
494    use super::*;
495
496    #[test]
497    fn test_error_codes() {
498        assert_eq!(ErrorCode::InvalidHash.code(), 1001);
499        assert_eq!(ErrorCode::EventNotFound.code(), 2001);
500        assert_eq!(ErrorCode::StorageRead.code(), 5001);
501    }
502
503    #[test]
504    fn test_error_categorization() {
505        assert!(ErrorCode::InvalidHash.is_client_error());
506        assert!(!ErrorCode::InvalidHash.is_server_error());
507
508        assert!(ErrorCode::StorageRead.is_server_error());
509        assert!(!ErrorCode::StorageRead.is_client_error());
510    }
511
512    #[test]
513    fn test_retryable() {
514        assert!(ErrorCode::StorageRead.is_retryable());
515        assert!(ErrorCode::Timeout.is_retryable());
516        assert!(!ErrorCode::InvalidHash.is_retryable());
517    }
518
519    #[test]
520    fn test_error_display() {
521        let e = Error::invalid_hash("bad hex");
522        assert!(e.to_string().contains("E1001"));
523        assert!(e.to_string().contains("bad hex"));
524    }
525
526    #[test]
527    fn test_error_code_display() {
528        assert_eq!(ErrorCode::InvalidHash.to_string(), "E1001");
529        assert_eq!(ErrorCode::Internal.to_string(), "E6003");
530    }
531
532    #[test]
533    fn test_from_bincode() {
534        // Try to deserialize invalid data as a String to trigger bincode error
535        let bad_data = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; // Invalid length prefix
536        let bincode_err: bincode::Error = bincode::deserialize::<String>(&bad_data).unwrap_err();
537        let err: Error = bincode_err.into();
538        assert_eq!(err.code(), ErrorCode::Serialization);
539        assert!(err.is_server_error());
540    }
541
542    #[test]
543    fn test_error_constructors() {
544        let e = Error::invalid_event("missing field");
545        assert_eq!(e.code(), ErrorCode::InvalidEvent);
546        assert!(e.is_client_error());
547
548        let e = Error::storage("disk full");
549        assert_eq!(e.code(), ErrorCode::StorageRead);
550        assert!(e.is_server_error());
551    }
552}