use thiserror::Error;
use crate::entry::ID;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum BackendError {
#[error("Entry not found: {id}")]
EntryNotFound {
id: ID,
},
#[error("Entry {entry_id} failed validation: {reason}")]
EntryValidationFailed {
entry_id: ID,
reason: String,
},
#[error("Verification status not found for entry: {id}")]
VerificationStatusNotFound {
id: ID,
},
#[error("Entry {entry_id} is not in tree {tree_id}")]
EntryNotInTree {
entry_id: ID,
tree_id: ID,
},
#[error("Entry {entry_id} is not in subtree {subtree} of tree {tree_id}")]
EntryNotInSubtree {
entry_id: ID,
tree_id: ID,
subtree: String,
},
#[error("Cycle detected in DAG while traversing from {entry_id}")]
CycleDetected {
entry_id: ID,
},
#[error("No common ancestor found for entries: {entry_ids:?}")]
NoCommonAncestor {
entry_ids: Vec<ID>,
},
#[error("No entry IDs provided for {operation}")]
EmptyEntryList {
operation: String,
},
#[error("Height calculation corruption: {reason}")]
HeightCalculationCorruption {
reason: String,
},
#[error("Private key not found: {key_name}")]
PrivateKeyNotFound {
key_name: String,
},
#[error("Serialization failed")]
SerializationFailed {
#[source]
source: serde_json::Error,
},
#[error("Deserialization failed")]
DeserializationFailed {
#[source]
source: serde_json::Error,
},
#[error("File I/O error")]
FileIo {
#[source]
source: std::io::Error,
},
#[error("CRDT cache operation failed: {reason}")]
CrdtCacheError {
reason: String,
},
#[error("Database integrity violation: {reason}")]
TreeIntegrityViolation {
reason: String,
},
#[error("Invalid tree reference: {tree_id}")]
InvalidTreeReference {
tree_id: String,
},
#[error("Database state inconsistency: {reason}")]
StateInconsistency {
reason: String,
},
#[error("Cache operation failed: {reason}")]
CacheError {
reason: String,
},
}
impl BackendError {
pub fn is_not_found(&self) -> bool {
matches!(
self,
BackendError::EntryNotFound { .. }
| BackendError::VerificationStatusNotFound { .. }
| BackendError::PrivateKeyNotFound { .. }
)
}
pub fn is_integrity_error(&self) -> bool {
matches!(
self,
BackendError::EntryValidationFailed { .. }
| BackendError::CycleDetected { .. }
| BackendError::HeightCalculationCorruption { .. }
| BackendError::TreeIntegrityViolation { .. }
| BackendError::StateInconsistency { .. }
)
}
pub fn is_io_error(&self) -> bool {
matches!(
self,
BackendError::FileIo { .. }
| BackendError::SerializationFailed { .. }
| BackendError::DeserializationFailed { .. }
)
}
pub fn is_cache_error(&self) -> bool {
matches!(
self,
BackendError::CrdtCacheError { .. } | BackendError::CacheError { .. }
)
}
pub fn is_logical_error(&self) -> bool {
matches!(
self,
BackendError::EntryNotInTree { .. }
| BackendError::EntryNotInSubtree { .. }
| BackendError::NoCommonAncestor { .. }
| BackendError::EmptyEntryList { .. }
)
}
pub fn entry_id(&self) -> Option<&ID> {
match self {
BackendError::EntryNotFound { id }
| BackendError::VerificationStatusNotFound { id }
| BackendError::EntryValidationFailed { entry_id: id, .. }
| BackendError::CycleDetected { entry_id: id }
| BackendError::EntryNotInTree { entry_id: id, .. }
| BackendError::EntryNotInSubtree { entry_id: id, .. } => Some(id),
_ => None,
}
}
pub fn tree_id(&self) -> Option<String> {
match self {
BackendError::EntryNotInTree { tree_id, .. }
| BackendError::EntryNotInSubtree { tree_id, .. } => Some(tree_id.to_string()),
BackendError::InvalidTreeReference { tree_id } => Some(tree_id.clone()),
_ => None,
}
}
}
impl From<BackendError> for crate::Error {
fn from(err: BackendError) -> Self {
crate::Error::Backend(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_helpers() {
let err = BackendError::EntryNotFound {
id: ID::from("test-entry"),
};
assert!(err.is_not_found());
assert_eq!(err.entry_id(), Some(&ID::from("test-entry")));
let err = BackendError::CycleDetected {
entry_id: ID::from("cycle-entry"),
};
assert!(err.is_integrity_error());
assert_eq!(err.entry_id(), Some(&ID::from("cycle-entry")));
let err = BackendError::FileIo {
source: std::io::Error::new(std::io::ErrorKind::NotFound, "test"),
};
assert!(err.is_io_error());
let err = BackendError::CacheError {
reason: "test".to_string(),
};
assert!(err.is_cache_error());
let err = BackendError::EmptyEntryList {
operation: "test".to_string(),
};
assert!(err.is_logical_error());
}
#[test]
fn test_error_conversion() {
let db_err = BackendError::EntryNotFound {
id: ID::from("test"),
};
let err: crate::Error = db_err.into();
match err {
crate::Error::Backend(BackendError::EntryNotFound { id }) => {
assert_eq!(id.to_string(), "test")
}
_ => panic!("Unexpected error variant"),
}
}
}