use thiserror::Error;
#[derive(Debug, Error)]
pub enum RepError {
#[error("node is not master: current master is {master:?}")]
NotMaster {
master: Option<String>,
},
#[error("node is not replica")]
NotReplica,
#[error("replication group does not exist: {0}")]
GroupNotFound(String),
#[error("node {0} not found in group")]
NodeNotFound(String),
#[error("election failed: {0}")]
ElectionFailed(String),
#[error("insufficient acks: needed {needed}, got {received}")]
InsufficientAcks {
needed: u32,
received: u32,
},
#[error("replica consistency timeout after {0:?}")]
ConsistencyTimeout(std::time::Duration),
#[error("replica lag too high: {lag_ms}ms exceeds limit {limit_ms}ms")]
ReplicaLagExceeded {
lag_ms: u64,
limit_ms: u64,
},
#[error("rollback required: from VLSN {from} to {to}")]
RollbackRequired {
from: i64,
to: i64,
},
#[error("network error: {0}")]
NetworkError(String),
#[error(
"frame corrupted: vlsn={vlsn} expected_crc={expected:#010x} actual_crc={actual:#010x}"
)]
FrameCorrupted { vlsn: u64, expected: u32, actual: u32 },
#[error("protocol error: {0}")]
ProtocolError(String),
#[error("node state error: {0}")]
StateError(String),
#[error("database error: {0}")]
DatabaseError(String),
#[error("configuration error: {0}")]
ConfigError(String),
#[error("shutdown in progress")]
ShutdownInProgress,
#[error("invalid state transition: {0}")]
InvalidStateTransition(String),
#[error("node already exists: {0}")]
NodeAlreadyExists(String),
#[error("channel closed: {0}")]
ChannelClosed(String),
#[error("service not found: {0}")]
ServiceNotFound(String),
#[error("subscription error: {0}")]
SubscriptionError(String),
#[error("network restore error: {0}")]
NetworkRestoreError(String),
#[error("environment closed")]
EnvironmentClosed,
#[error("invalid state: {0}")]
InvalidState(String),
}
pub type Result<T> = std::result::Result<T, RepError>;
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_not_master_with_master() {
let err = RepError::NotMaster { master: Some("node1".to_string()) };
assert_eq!(
err.to_string(),
"node is not master: current master is Some(\"node1\")"
);
}
#[test]
fn test_not_master_without_master() {
let err = RepError::NotMaster { master: None };
assert_eq!(
err.to_string(),
"node is not master: current master is None"
);
}
#[test]
fn test_not_replica() {
let err = RepError::NotReplica;
assert_eq!(err.to_string(), "node is not replica");
}
#[test]
fn test_group_not_found() {
let err = RepError::GroupNotFound("mygroup".to_string());
assert_eq!(
err.to_string(),
"replication group does not exist: mygroup"
);
}
#[test]
fn test_node_not_found() {
let err = RepError::NodeNotFound("node2".to_string());
assert_eq!(err.to_string(), "node node2 not found in group");
}
#[test]
fn test_election_failed() {
let err = RepError::ElectionFailed("no quorum".to_string());
assert_eq!(err.to_string(), "election failed: no quorum");
}
#[test]
fn test_insufficient_acks() {
let err = RepError::InsufficientAcks { needed: 3, received: 1 };
assert_eq!(err.to_string(), "insufficient acks: needed 3, got 1");
}
#[test]
fn test_consistency_timeout() {
let err = RepError::ConsistencyTimeout(Duration::from_secs(5));
assert_eq!(err.to_string(), "replica consistency timeout after 5s");
}
#[test]
fn test_replica_lag_exceeded() {
let err = RepError::ReplicaLagExceeded { lag_ms: 5000, limit_ms: 1000 };
assert_eq!(
err.to_string(),
"replica lag too high: 5000ms exceeds limit 1000ms"
);
}
#[test]
fn test_rollback_required() {
let err = RepError::RollbackRequired { from: 100, to: 50 };
assert_eq!(err.to_string(), "rollback required: from VLSN 100 to 50");
}
#[test]
fn test_network_error() {
let err = RepError::NetworkError("connection refused".to_string());
assert_eq!(err.to_string(), "network error: connection refused");
}
#[test]
fn test_protocol_error() {
let err = RepError::ProtocolError("version mismatch".to_string());
assert_eq!(err.to_string(), "protocol error: version mismatch");
}
#[test]
fn test_state_error() {
let err = RepError::StateError("not initialized".to_string());
assert_eq!(err.to_string(), "node state error: not initialized");
}
#[test]
fn test_database_error() {
let err = RepError::DatabaseError("corrupt log".to_string());
assert_eq!(err.to_string(), "database error: corrupt log");
}
#[test]
fn test_config_error() {
let err = RepError::ConfigError("invalid port".to_string());
assert_eq!(err.to_string(), "configuration error: invalid port");
}
#[test]
fn test_shutdown_in_progress() {
let err = RepError::ShutdownInProgress;
assert_eq!(err.to_string(), "shutdown in progress");
}
#[test]
fn test_result_type_alias() {
let ok: Result<u32> = Ok(42);
assert!(ok.is_ok_and(|v| v == 42));
let err: Result<u32> = Err(RepError::NotReplica);
assert!(err.is_err());
}
}