use core::fmt;
use error_forge::ForgeError;
pub type Result<T, E = TxnError> = core::result::Result<T, E>;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TxnError {
Conflict {
key_len: usize,
},
Store {
context: &'static str,
detail: String,
},
Durability {
detail: String,
},
}
impl TxnError {
#[inline]
#[must_use]
pub fn conflict(key_len: usize) -> Self {
TxnError::Conflict { key_len }
}
#[inline]
#[must_use]
pub fn store(context: &'static str, detail: impl fmt::Display) -> Self {
TxnError::Store {
context,
detail: detail.to_string(),
}
}
#[cfg(feature = "durability")]
#[inline]
#[must_use]
pub(crate) fn durability(detail: impl fmt::Display) -> Self {
TxnError::Durability {
detail: detail.to_string(),
}
}
#[inline]
#[must_use]
pub fn is_retryable(&self) -> bool {
matches!(self, TxnError::Conflict { .. })
}
}
impl fmt::Display for TxnError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TxnError::Conflict { key_len } => write!(
f,
"write-write conflict on a {key_len}-byte key; retry the transaction"
),
TxnError::Store { context, detail } => {
write!(f, "version store error while {context}: {detail}")
}
TxnError::Durability { detail } => {
write!(f, "durable commit log error: {detail}")
}
}
}
}
impl core::error::Error for TxnError {}
impl ForgeError for TxnError {
fn kind(&self) -> &'static str {
match self {
TxnError::Conflict { .. } => "Conflict",
TxnError::Store { .. } => "Store",
TxnError::Durability { .. } => "Durability",
}
}
fn caption(&self) -> &'static str {
"transaction error"
}
fn is_fatal(&self) -> bool {
matches!(self, TxnError::Durability { .. })
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_conflict_is_retryable() {
assert!(TxnError::conflict(8).is_retryable());
}
#[test]
fn test_store_error_is_not_retryable() {
assert!(!TxnError::store("read", "disk gone").is_retryable());
}
#[test]
fn test_conflict_display_reports_key_len_not_bytes() {
let msg = TxnError::conflict(16).to_string();
assert!(msg.contains("16-byte"));
assert!(msg.contains("retry"));
}
#[test]
fn test_kind_matches_variant() {
assert_eq!(TxnError::conflict(1).kind(), "Conflict");
assert_eq!(TxnError::store("x", "y").kind(), "Store");
}
#[test]
fn test_no_variant_is_fatal() {
assert!(!TxnError::conflict(1).is_fatal());
assert!(!TxnError::store("x", "y").is_fatal());
}
#[test]
fn test_error_is_clonable_and_comparable() {
let a = TxnError::conflict(4);
assert_eq!(a.clone(), a);
}
}