kstone_core/
error.rs

1use std::io;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum Error {
6    #[error("IO error: {0}")]
7    Io(#[from] io::Error),
8
9    #[error("Corruption detected: {0}")]
10    Corruption(String),
11
12    #[error("Key not found: {0}")]
13    NotFound(String),
14
15    #[error("Invalid argument: {0}")]
16    InvalidArgument(String),
17
18    #[error("Database already exists: {0}")]
19    AlreadyExists(String),
20
21    #[error("WAL full")]
22    WalFull,
23
24    #[error("Checksum mismatch")]
25    ChecksumMismatch,
26
27    #[error("Internal error: {0}")]
28    Internal(String),
29
30    // Phase 1 additions
31    #[error("Encryption error: {0}")]
32    EncryptionError(String),
33
34    #[error("Compression error: {0}")]
35    CompressionError(String),
36
37    #[error("Manifest corruption: {0}")]
38    ManifestCorruption(String),
39
40    #[error("Compaction error: {0}")]
41    CompactionError(String),
42
43    #[error("Stripe error: {0}")]
44    StripeError(String),
45
46    // Phase 2.3 additions
47    #[error("Invalid expression: {0}")]
48    InvalidExpression(String),
49
50    // Phase 2.5 additions
51    #[error("Conditional check failed: {0}")]
52    ConditionalCheckFailed(String),
53
54    // Phase 2.7 additions
55    #[error("Transaction canceled: {0}")]
56    TransactionCanceled(String),
57
58    // Phase 4 additions
59    #[error("Invalid query: {0}")]
60    InvalidQuery(String),
61
62    // Phase 8 additions
63    #[error("Resource exhausted: {0}")]
64    ResourceExhausted(String),
65}
66
67impl Error {
68    /// Returns a stable error code for this error variant.
69    /// These codes are stable and can be used by clients for error classification.
70    pub fn code(&self) -> &'static str {
71        match self {
72            Error::Io(_) => "IO_ERROR",
73            Error::Corruption(_) => "CORRUPTION",
74            Error::NotFound(_) => "NOT_FOUND",
75            Error::InvalidArgument(_) => "INVALID_ARGUMENT",
76            Error::AlreadyExists(_) => "ALREADY_EXISTS",
77            Error::WalFull => "WAL_FULL",
78            Error::ChecksumMismatch => "CHECKSUM_MISMATCH",
79            Error::Internal(_) => "INTERNAL_ERROR",
80            Error::EncryptionError(_) => "ENCRYPTION_ERROR",
81            Error::CompressionError(_) => "COMPRESSION_ERROR",
82            Error::ManifestCorruption(_) => "MANIFEST_CORRUPTION",
83            Error::CompactionError(_) => "COMPACTION_ERROR",
84            Error::StripeError(_) => "STRIPE_ERROR",
85            Error::InvalidExpression(_) => "INVALID_EXPRESSION",
86            Error::ConditionalCheckFailed(_) => "CONDITIONAL_CHECK_FAILED",
87            Error::TransactionCanceled(_) => "TRANSACTION_CANCELED",
88            Error::InvalidQuery(_) => "INVALID_QUERY",
89            Error::ResourceExhausted(_) => "RESOURCE_EXHAUSTED",
90        }
91    }
92
93    /// Returns true if this error is potentially retryable.
94    ///
95    /// Transient errors like IO errors are retryable, while logical errors
96    /// like InvalidArgument or ConditionalCheckFailed are not.
97    pub fn is_retryable(&self) -> bool {
98        match self {
99            // Retryable errors (transient)
100            Error::Io(_) => true,
101            Error::WalFull => true,
102            Error::ResourceExhausted(_) => true,
103            Error::CompactionError(_) => true,
104            Error::StripeError(_) => true,
105
106            // Non-retryable errors (logical/permanent)
107            Error::Corruption(_) => false,
108            Error::NotFound(_) => false,
109            Error::InvalidArgument(_) => false,
110            Error::AlreadyExists(_) => false,
111            Error::ChecksumMismatch => false,
112            Error::Internal(_) => false,
113            Error::EncryptionError(_) => false,
114            Error::CompressionError(_) => false,
115            Error::ManifestCorruption(_) => false,
116            Error::InvalidExpression(_) => false,
117            Error::ConditionalCheckFailed(_) => false,
118            Error::TransactionCanceled(_) => false,
119            Error::InvalidQuery(_) => false,
120        }
121    }
122
123    /// Adds context to an error by wrapping it in an Internal error.
124    ///
125    /// This is useful for adding operation context to errors that propagate
126    /// from lower layers.
127    ///
128    /// # Examples
129    ///
130    /// ```no_run
131    /// use kstone_core::Error;
132    ///
133    /// fn write_data() -> Result<(), Error> {
134    ///     // Some operation that might fail
135    ///     Err(Error::Io(std::io::Error::new(
136    ///         std::io::ErrorKind::NotFound,
137    ///         "file not found"
138    ///     )))
139    /// }
140    ///
141    /// fn save_record() -> Result<(), Error> {
142    ///     write_data().map_err(|e| e.with_context("failed to save record"))
143    /// }
144    /// ```
145    pub fn with_context(self, context: &str) -> Error {
146        Error::Internal(format!("{}: {}", context, self))
147    }
148}
149
150pub type Result<T> = std::result::Result<T, Error>;