Skip to main content

azoth_core/
error.rs

1use std::io;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum AzothError {
6    #[error("IO error: {0}")]
7    Io(#[from] io::Error),
8
9    #[error("Serialization error: {0}")]
10    Serialization(String),
11
12    #[error("Transaction error: {0}")]
13    Transaction(String),
14
15    #[error("Store is sealed at event {0}")]
16    Sealed(u64),
17
18    #[error("Store is paused, ingestion suspended")]
19    Paused,
20
21    #[error("Configuration error: {0}")]
22    Config(String),
23
24    #[error("Backup error: {0}")]
25    Backup(String),
26
27    #[error("Restore error: {0}")]
28    Restore(String),
29
30    #[error("Encryption error: {0}")]
31    Encryption(String),
32
33    #[error("Projection error: {0}")]
34    Projection(String),
35
36    #[error("Event decoding error: {0}")]
37    EventDecode(String),
38
39    #[error("Preflight validation error: {0}")]
40    PreflightFailed(String),
41
42    #[error("Lock error: {0}")]
43    Lock(String),
44
45    #[error("Not found: {0}")]
46    NotFound(String),
47
48    #[error("Invalid state: {0}")]
49    InvalidState(String),
50
51    #[error("Timeout: {0}")]
52    Timeout(String),
53
54    #[error(
55        "Lock acquisition timed out after {timeout_ms}ms (possible deadlock or high contention)"
56    )]
57    LockTimeout { timeout_ms: u64 },
58
59    #[error("Attempted to access undeclared key '{key}' - all keys must be declared via keys() before access")]
60    UndeclaredKeyAccess { key: String },
61
62    #[error("Circuit breaker is open, rejecting request")]
63    CircuitBreakerOpen,
64
65    #[error("Internal error: {0}")]
66    Internal(String),
67
68    #[error("Other error: {0}")]
69    Other(#[from] anyhow::Error),
70}
71
72pub type Result<T> = std::result::Result<T, AzothError>;
73
74impl AzothError {
75    /// Wrap this error with additional context.
76    ///
77    /// The context string is prepended to the error message, producing a
78    /// chain like `"during balance update: Transaction error: ..."`.
79    ///
80    /// # Example
81    /// ```ignore
82    /// db.write_txn()
83    ///     .map_err(|e| e.context("during balance update"))?;
84    /// ```
85    pub fn context(self, msg: impl Into<String>) -> Self {
86        let ctx = msg.into();
87        AzothError::Internal(format!("{}: {}", ctx, self))
88    }
89}
90
91/// Extension trait to add `.context()` on `Result<T, AzothError>`.
92///
93/// Mirrors the ergonomics of `anyhow::Context`.
94pub trait ResultExt<T> {
95    /// If the result is `Err`, wrap the error with additional context.
96    fn context(self, msg: impl Into<String>) -> Result<T>;
97
98    /// If the result is `Err`, wrap the error with a lazily-evaluated context.
99    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T>;
100}
101
102impl<T> ResultExt<T> for Result<T> {
103    fn context(self, msg: impl Into<String>) -> Result<T> {
104        self.map_err(|e| e.context(msg))
105    }
106
107    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T> {
108        self.map_err(|e| e.context(f()))
109    }
110}
111
112// Custom Error Types:
113//
114// Azoth supports custom error types through the `#[from] anyhow::Error` variant.
115// Any error implementing `std::error::Error + Send + Sync + 'static` can be
116// converted to `AzothError::Other`.
117//
118// For better control, implement `From<YourError> for AzothError` directly.
119//
120// Example:
121//
122// use thiserror::Error;
123//
124// #[derive(Error, Debug)]
125// pub enum MyAppError {
126//     #[error("Business logic error: {0}")]
127//     BusinessLogic(String),
128//
129//     #[error("Authorization error: {0}")]
130//     Unauthorized(String),
131//
132//     #[error(transparent)]
133//     Azoth(#[from] AzothError),
134// }
135//
136// impl From<MyAppError> for AzothError {
137//     fn from(err: MyAppError) -> Self {
138//         match err {
139//             MyAppError::Azoth(e) => e,
140//             other => AzothError::Other(other.into()),
141//         }
142//     }
143// }