Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica

eidetica/transaction/
errors.rs

1//! Transaction specific errors
2//!
3//! This module contains error types specific to transactions, which compose
4//! errors from multiple modules into a cohesive error handling system for
5//! cross-module operations.
6
7use thiserror::Error;
8
9/// Errors that can occur during transactions
10///
11/// `TransactionError` represents failures specific to transactions that
12/// span multiple modules and require coordinated error handling. These errors
13/// typically occur during entry construction, commit operations, and cross-module
14/// data staging.
15#[non_exhaustive]
16#[derive(Debug, Error)]
17pub enum TransactionError {
18    /// Transaction has already been committed and cannot be used again
19    #[error("Transaction has already been committed")]
20    TransactionAlreadyCommitted,
21
22    /// Tips array cannot be empty when creating a transaction
23    #[error("Empty tips array not allowed for transaction")]
24    EmptyTipsNotAllowed,
25
26    /// Invalid tip provided to transaction
27    #[error("Invalid tip for transaction: {tip_id}")]
28    InvalidTip { tip_id: String },
29
30    /// Entry construction failed during commit
31    #[error("Entry construction failed: {reason}")]
32    EntryConstructionFailed { reason: String },
33
34    /// Entry signing failed during commit
35    #[error("Entry signing failed for key '{key_name}': {reason}")]
36    EntrySigningFailed { key_name: String, reason: String },
37
38    /// Required signing key not found
39    #[error("Signing key not found: {key_name}")]
40    SigningKeyNotFound { key_name: String },
41
42    /// Authentication is required but not configured
43    #[error("Authentication required but not configured")]
44    AuthenticationRequired,
45
46    /// Authentication configuration is missing
47    #[error("No authentication configuration found")]
48    NoAuthConfiguration,
49
50    /// Authentication configuration exists but is corrupted or malformed
51    #[error("Authentication configuration is corrupted or malformed")]
52    CorruptedAuthConfiguration,
53
54    /// Insufficient permissions for the operation
55    #[error("Insufficient permissions for operation")]
56    InsufficientPermissions,
57
58    /// Entry signature verification failed
59    #[error("Entry signature verification failed")]
60    SignatureVerificationFailed,
61
62    /// Entry validation failed (signature, permissions, or configuration)
63    #[error("Entry validation failed")]
64    EntryValidationFailed,
65
66    /// Store data deserialization failed
67    #[error("Store data deserialization failed for '{store}': {reason}")]
68    StoreDeserializationFailed { store: String, reason: String },
69
70    /// Backend operation failed during commit
71    #[error("Backend operation failed during commit: {reason}")]
72    BackendOperationFailed { reason: String },
73}
74
75impl TransactionError {
76    /// Check if this error indicates the operation was already committed
77    pub fn is_already_committed(&self) -> bool {
78        matches!(self, TransactionError::TransactionAlreadyCommitted)
79    }
80
81    /// Check if this error is authentication-related
82    pub fn is_authentication_error(&self) -> bool {
83        matches!(
84            self,
85            TransactionError::SigningKeyNotFound { .. }
86                | TransactionError::AuthenticationRequired
87                | TransactionError::NoAuthConfiguration
88                | TransactionError::CorruptedAuthConfiguration
89                | TransactionError::InsufficientPermissions
90                | TransactionError::SignatureVerificationFailed
91                | TransactionError::EntrySigningFailed { .. }
92                | TransactionError::EntryValidationFailed
93        )
94    }
95
96    /// Check if this error is related to entry operations
97    pub fn is_entry_error(&self) -> bool {
98        matches!(
99            self,
100            TransactionError::EntryConstructionFailed { .. }
101                | TransactionError::EntrySigningFailed { .. }
102                | TransactionError::SignatureVerificationFailed
103                | TransactionError::EntryValidationFailed
104        )
105    }
106
107    /// Check if this error is related to store operations
108    pub fn is_store_error(&self) -> bool {
109        matches!(self, TransactionError::StoreDeserializationFailed { .. })
110    }
111
112    /// Check if this error is related to backend operations
113    pub fn is_backend_error(&self) -> bool {
114        matches!(self, TransactionError::BackendOperationFailed { .. })
115    }
116
117    /// Check if this error is related to validation
118    pub fn is_validation_error(&self) -> bool {
119        matches!(
120            self,
121            TransactionError::InvalidTip { .. } | TransactionError::EmptyTipsNotAllowed
122        )
123    }
124
125    /// Get the store name if this is a store-related error
126    pub fn store_name(&self) -> Option<&str> {
127        match self {
128            TransactionError::StoreDeserializationFailed { store, .. } => Some(store),
129            _ => None,
130        }
131    }
132
133    /// Get the key name if this is an authentication-related error
134    pub fn key_name(&self) -> Option<&str> {
135        match self {
136            TransactionError::SigningKeyNotFound { key_name }
137            | TransactionError::EntrySigningFailed { key_name, .. } => Some(key_name),
138            _ => None,
139        }
140    }
141}
142
143// Conversion from TransactionError to the main Error type
144impl From<TransactionError> for crate::Error {
145    fn from(err: TransactionError) -> Self {
146        crate::Error::Transaction(err)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_error_classification() {
156        // Test authentication errors
157        let auth_err = TransactionError::AuthenticationRequired;
158        assert!(auth_err.is_authentication_error());
159        assert!(!auth_err.is_entry_error());
160
161        // Test entry errors
162        let entry_err = TransactionError::EntryConstructionFailed {
163            reason: "test".to_owned(),
164        };
165        assert!(entry_err.is_entry_error());
166        assert!(!entry_err.is_authentication_error());
167
168        // Test store errors
169        let store_err = TransactionError::StoreDeserializationFailed {
170            store: "test_store".to_owned(),
171            reason: "test".to_owned(),
172        };
173        assert!(store_err.is_store_error());
174        assert_eq!(store_err.store_name(), Some("test_store"));
175
176        // Test validation errors
177        let validation_err = TransactionError::EmptyTipsNotAllowed;
178        assert!(validation_err.is_validation_error());
179        assert!(!validation_err.is_backend_error());
180    }
181
182    #[test]
183    fn test_already_committed() {
184        let err = TransactionError::TransactionAlreadyCommitted;
185        assert!(err.is_already_committed());
186    }
187
188    #[test]
189    fn test_key_name_extraction() {
190        let err = TransactionError::SigningKeyNotFound {
191            key_name: "test_key".to_owned(),
192        };
193        assert_eq!(err.key_name(), Some("test_key"));
194
195        let other_err = TransactionError::AuthenticationRequired;
196        assert_eq!(other_err.key_name(), None);
197    }
198}