use thiserror::Error;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum TransactionError {
#[error("Transaction has already been committed")]
TransactionAlreadyCommitted,
#[error("Empty tips array not allowed for transaction")]
EmptyTipsNotAllowed,
#[error("Invalid tip for transaction: {tip_id}")]
InvalidTip { tip_id: String },
#[error("Entry construction failed: {reason}")]
EntryConstructionFailed { reason: String },
#[error("Entry signing failed for key '{key_name}': {reason}")]
EntrySigningFailed { key_name: String, reason: String },
#[error("Signing key not found: {key_name}")]
SigningKeyNotFound { key_name: String },
#[error("Authentication required but not configured")]
AuthenticationRequired,
#[error("No authentication configuration found")]
NoAuthConfiguration,
#[error("Authentication configuration is corrupted or malformed")]
CorruptedAuthConfiguration,
#[error("Insufficient permissions for operation")]
InsufficientPermissions,
#[error("Entry signature verification failed")]
SignatureVerificationFailed,
#[error("Store data deserialization failed for '{store}': {reason}")]
StoreDeserializationFailed { store: String, reason: String },
#[error("Backend operation failed during commit: {reason}")]
BackendOperationFailed { reason: String },
}
impl TransactionError {
pub fn is_already_committed(&self) -> bool {
matches!(self, TransactionError::TransactionAlreadyCommitted)
}
pub fn is_authentication_error(&self) -> bool {
matches!(
self,
TransactionError::SigningKeyNotFound { .. }
| TransactionError::AuthenticationRequired
| TransactionError::NoAuthConfiguration
| TransactionError::CorruptedAuthConfiguration
| TransactionError::InsufficientPermissions
| TransactionError::SignatureVerificationFailed
| TransactionError::EntrySigningFailed { .. }
)
}
pub fn is_entry_error(&self) -> bool {
matches!(
self,
TransactionError::EntryConstructionFailed { .. }
| TransactionError::EntrySigningFailed { .. }
| TransactionError::SignatureVerificationFailed
)
}
pub fn is_store_error(&self) -> bool {
matches!(self, TransactionError::StoreDeserializationFailed { .. })
}
pub fn is_backend_error(&self) -> bool {
matches!(self, TransactionError::BackendOperationFailed { .. })
}
pub fn is_validation_error(&self) -> bool {
matches!(
self,
TransactionError::InvalidTip { .. } | TransactionError::EmptyTipsNotAllowed
)
}
pub fn store_name(&self) -> Option<&str> {
match self {
TransactionError::StoreDeserializationFailed { store, .. } => Some(store),
_ => None,
}
}
pub fn key_name(&self) -> Option<&str> {
match self {
TransactionError::SigningKeyNotFound { key_name }
| TransactionError::EntrySigningFailed { key_name, .. } => Some(key_name),
_ => None,
}
}
}
impl From<TransactionError> for crate::Error {
fn from(err: TransactionError) -> Self {
crate::Error::Transaction(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_classification() {
let auth_err = TransactionError::AuthenticationRequired;
assert!(auth_err.is_authentication_error());
assert!(!auth_err.is_entry_error());
let entry_err = TransactionError::EntryConstructionFailed {
reason: "test".to_owned(),
};
assert!(entry_err.is_entry_error());
assert!(!entry_err.is_authentication_error());
let store_err = TransactionError::StoreDeserializationFailed {
store: "test_store".to_owned(),
reason: "test".to_owned(),
};
assert!(store_err.is_store_error());
assert_eq!(store_err.store_name(), Some("test_store"));
let validation_err = TransactionError::EmptyTipsNotAllowed;
assert!(validation_err.is_validation_error());
assert!(!validation_err.is_backend_error());
}
#[test]
fn test_already_committed() {
let err = TransactionError::TransactionAlreadyCommitted;
assert!(err.is_already_committed());
}
#[test]
fn test_key_name_extraction() {
let err = TransactionError::SigningKeyNotFound {
key_name: "test_key".to_owned(),
};
assert_eq!(err.key_name(), Some("test_key"));
let other_err = TransactionError::AuthenticationRequired;
assert_eq!(other_err.key_name(), None);
}
}