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("Event log write failed after state commit (events saved to dead letter queue): {0}")]
66    EventLogWriteFailed(String),
67
68    #[error("Key too large ({size} bytes, max {max}): {context}")]
69    KeyTooLarge {
70        size: usize,
71        max: usize,
72        context: String,
73    },
74
75    #[error("Value too large ({size} bytes, max {max}): {context}")]
76    ValueTooLarge {
77        size: usize,
78        max: usize,
79        context: String,
80    },
81
82    #[error("Internal error: {0}")]
83    Internal(String),
84
85    #[error("Other error: {0}")]
86    Other(#[from] anyhow::Error),
87}
88
89pub type Result<T> = std::result::Result<T, AzothError>;
90
91impl AzothError {
92    /// Wrap this error with additional context.
93    ///
94    /// The context string is prepended to the error message, producing a
95    /// chain like `"during balance update: Transaction error: ..."`.
96    ///
97    /// # Example
98    /// ```ignore
99    /// db.write_txn()
100    ///     .map_err(|e| e.context("during balance update"))?;
101    /// ```
102    pub fn context(self, msg: impl Into<String>) -> Self {
103        let ctx = msg.into();
104        AzothError::Internal(format!("{}: {}", ctx, self))
105    }
106}
107
108/// Extension trait to add `.context()` on `Result<T, AzothError>`.
109///
110/// Mirrors the ergonomics of `anyhow::Context`.
111pub trait ResultExt<T> {
112    /// If the result is `Err`, wrap the error with additional context.
113    fn context(self, msg: impl Into<String>) -> Result<T>;
114
115    /// If the result is `Err`, wrap the error with a lazily-evaluated context.
116    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T>;
117}
118
119impl<T> ResultExt<T> for Result<T> {
120    fn context(self, msg: impl Into<String>) -> Result<T> {
121        self.map_err(|e| e.context(msg))
122    }
123
124    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T> {
125        self.map_err(|e| e.context(f()))
126    }
127}
128
129// Custom Error Types:
130//
131// Azoth supports custom error types through the `#[from] anyhow::Error` variant.
132// Any error implementing `std::error::Error + Send + Sync + 'static` can be
133// converted to `AzothError::Other`.
134//
135// For better control, implement `From<YourError> for AzothError` directly.
136//
137// Example:
138//
139// use thiserror::Error;
140//
141// #[derive(Error, Debug)]
142// pub enum MyAppError {
143//     #[error("Business logic error: {0}")]
144//     BusinessLogic(String),
145//
146//     #[error("Authorization error: {0}")]
147//     Unauthorized(String),
148//
149//     #[error(transparent)]
150//     Azoth(#[from] AzothError),
151// }
152//
153// impl From<MyAppError> for AzothError {
154//     fn from(err: MyAppError) -> Self {
155//         match err {
156//             MyAppError::Azoth(e) => e,
157//             other => AzothError::Other(other.into()),
158//         }
159//     }
160// }