eidetica/transaction/
errors.rs1use thiserror::Error;
8
9use crate::entry::ID;
10
11#[non_exhaustive]
18#[derive(Debug, Error)]
19pub enum TransactionError {
20 #[error("Transaction has already been committed")]
22 TransactionAlreadyCommitted,
23
24 #[error("Empty tips array not allowed for transaction")]
26 EmptyTipsNotAllowed,
27
28 #[error("Invalid tip for transaction: {tip_id}")]
30 InvalidTip { tip_id: ID },
31
32 #[error("Entry construction failed: {reason}")]
34 EntryConstructionFailed { reason: String },
35
36 #[error("Entry signing failed for key '{key_name}': {reason}")]
38 EntrySigningFailed { key_name: String, reason: String },
39
40 #[error("Signing key not found: {key_name}")]
42 SigningKeyNotFound { key_name: String },
43
44 #[error("Authentication required but not configured")]
46 AuthenticationRequired,
47
48 #[error("No authentication configuration found")]
50 NoAuthConfiguration,
51
52 #[error("Authentication configuration is corrupted or malformed")]
54 CorruptedAuthConfiguration,
55
56 #[error("Insufficient permissions for operation")]
58 InsufficientPermissions,
59
60 #[error("Entry signature verification failed")]
62 SignatureVerificationFailed,
63
64 #[error("Entry validation failed")]
66 EntryValidationFailed,
67
68 #[error("Store data deserialization failed for '{store}': {reason}")]
70 StoreDeserializationFailed { store: String, reason: String },
71
72 #[error("Backend operation failed during commit: {reason}")]
74 BackendOperationFailed { reason: String },
75
76 #[error(
81 "System subtree '{name}' is locked for this transaction (init callbacks may not open `_`-prefixed subtrees directly)"
82 )]
83 SystemSubtreeLocked { name: String },
84}
85
86impl TransactionError {
87 pub fn is_already_committed(&self) -> bool {
89 matches!(self, TransactionError::TransactionAlreadyCommitted)
90 }
91
92 pub fn is_authentication_error(&self) -> bool {
94 matches!(
95 self,
96 TransactionError::SigningKeyNotFound { .. }
97 | TransactionError::AuthenticationRequired
98 | TransactionError::NoAuthConfiguration
99 | TransactionError::CorruptedAuthConfiguration
100 | TransactionError::InsufficientPermissions
101 | TransactionError::SignatureVerificationFailed
102 | TransactionError::EntrySigningFailed { .. }
103 | TransactionError::EntryValidationFailed
104 )
105 }
106
107 pub fn is_entry_error(&self) -> bool {
109 matches!(
110 self,
111 TransactionError::EntryConstructionFailed { .. }
112 | TransactionError::EntrySigningFailed { .. }
113 | TransactionError::SignatureVerificationFailed
114 | TransactionError::EntryValidationFailed
115 )
116 }
117
118 pub fn is_store_error(&self) -> bool {
120 matches!(self, TransactionError::StoreDeserializationFailed { .. })
121 }
122
123 pub fn is_backend_error(&self) -> bool {
125 matches!(self, TransactionError::BackendOperationFailed { .. })
126 }
127
128 pub fn is_validation_error(&self) -> bool {
130 matches!(
131 self,
132 TransactionError::InvalidTip { .. } | TransactionError::EmptyTipsNotAllowed
133 )
134 }
135
136 pub fn store_name(&self) -> Option<&str> {
138 match self {
139 TransactionError::StoreDeserializationFailed { store, .. } => Some(store),
140 _ => None,
141 }
142 }
143
144 pub fn key_name(&self) -> Option<&str> {
146 match self {
147 TransactionError::SigningKeyNotFound { key_name }
148 | TransactionError::EntrySigningFailed { key_name, .. } => Some(key_name),
149 _ => None,
150 }
151 }
152}
153
154impl From<TransactionError> for crate::Error {
156 fn from(err: TransactionError) -> Self {
157 crate::Error::Transaction(Box::new(err))
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_error_classification() {
167 let auth_err = TransactionError::AuthenticationRequired;
169 assert!(auth_err.is_authentication_error());
170 assert!(!auth_err.is_entry_error());
171
172 let entry_err = TransactionError::EntryConstructionFailed {
174 reason: "test".to_owned(),
175 };
176 assert!(entry_err.is_entry_error());
177 assert!(!entry_err.is_authentication_error());
178
179 let store_err = TransactionError::StoreDeserializationFailed {
181 store: "test_store".to_owned(),
182 reason: "test".to_owned(),
183 };
184 assert!(store_err.is_store_error());
185 assert_eq!(store_err.store_name(), Some("test_store"));
186
187 let validation_err = TransactionError::EmptyTipsNotAllowed;
189 assert!(validation_err.is_validation_error());
190 assert!(!validation_err.is_backend_error());
191 }
192
193 #[test]
194 fn test_already_committed() {
195 let err = TransactionError::TransactionAlreadyCommitted;
196 assert!(err.is_already_committed());
197 }
198
199 #[test]
200 fn test_key_name_extraction() {
201 let err = TransactionError::SigningKeyNotFound {
202 key_name: "test_key".to_owned(),
203 };
204 assert_eq!(err.key_name(), Some("test_key"));
205
206 let other_err = TransactionError::AuthenticationRequired;
207 assert_eq!(other_err.key_name(), None);
208 }
209}