use thiserror::Error;
pub type Result<T> = std::result::Result<T, ClusterError>;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum CalvinError {
#[error("write set is empty; a Calvin transaction must write at least one key")]
EmptyWriteSet,
#[error(
"write set resolves to a single vshard ({vshard}); \
use the single-shard fast path instead"
)]
SingleVshardTxn { vshard: u32 },
#[error("sequencer error: {0}")]
Sequencer(#[from] crate::calvin::sequencer::error::SequencerError),
}
#[derive(Debug, Error)]
pub enum MigrationCheckpointError {
#[error(
"crc32c mismatch on migration checkpoint for {migration_id}: expected {expected:#010x} got {actual:#010x}"
)]
Crc32cMismatch {
migration_id: uuid::Uuid,
expected: u32,
actual: u32,
},
#[error("codec error persisting migration checkpoint: {detail}")]
Codec { detail: String },
#[error("storage error persisting migration checkpoint: {detail}")]
Storage { detail: String },
}
#[derive(Debug, Error)]
pub enum MigrationRecoveryError {
#[error("compensation failed for migration {migration_id} step {step}: {detail}")]
CompensationFailed {
migration_id: uuid::Uuid,
step: usize,
detail: String,
},
#[error("storage error during migration recovery: {detail}")]
Storage { detail: String },
#[error("codec error during migration recovery: {detail}")]
Codec { detail: String },
}
#[derive(Debug, Error)]
pub enum ClusterError {
#[error("raft error: {0}")]
Raft(#[from] nodedb_raft::RaftError),
#[error("vshard {vshard_id} not mapped to any raft group")]
VShardNotMapped { vshard_id: u32 },
#[error("raft group {group_id} not found on this node")]
GroupNotFound { group_id: u64 },
#[error("migration in progress for vshard {vshard_id}")]
MigrationInProgress { vshard_id: u32 },
#[error("migration refused: estimated pause {estimated_us}µs exceeds budget {budget_us}µs")]
MigrationPauseBudgetExceeded { estimated_us: u64, budget_us: u64 },
#[error("node {node_id} not reachable")]
NodeUnreachable { node_id: u64 },
#[error("ghost stub not found: node={node_id} on shard={shard_id}")]
GhostNotFound { node_id: String, shard_id: u32 },
#[error("transport error: {detail}")]
Transport { detail: String },
#[error("storage error: {detail}")]
Storage { detail: String },
#[error("codec error: {detail}")]
Codec { detail: String },
#[error(
"unsupported wire version: got {got}, this node accepts [{supported_min}..={supported_max}]"
)]
UnsupportedWireVersion {
got: u8,
supported_min: u8,
supported_max: u8,
},
#[error("circuit open for node {node_id}: peer has {failures} consecutive failures")]
CircuitOpen { node_id: u64, failures: u32 },
#[error("raft group {group_id} disappeared while waiting for conf change commit")]
JoinGroupDisappeared { group_id: u64 },
#[error("conf change commit timeout on group {group_id} (waited for index {log_index})")]
JoinCommitTimeout { group_id: u64, log_index: u64 },
#[error("invalid cluster configuration: {detail}")]
Config { detail: String },
#[error("migration checkpoint error: {0}")]
MigrationCheckpoint(#[from] MigrationCheckpointError),
#[error("migration recovery error: {0}")]
MigrationRecovery(#[from] MigrationRecoveryError),
#[error(
"vshard {vshard_id} misrouted: this node is no longer the owner\
{}", if let Some(n) = expected_owner_node { format!("; current owner may be node {n}") } else { String::new() }
)]
WrongOwner {
vshard_id: u32,
expected_owner_node: Option<u64>,
},
#[error("calvin error: {0}")]
Calvin(#[from] CalvinError),
#[error(
"snapshot CRC mismatch for group {group_id}: stored {stored:#010x}, computed {computed:#010x}"
)]
SnapshotCrcMismatch {
group_id: u64,
stored: u32,
computed: u32,
},
#[error("snapshot offset regression for group {group_id}: expected {expected}, got {actual}")]
SnapshotOffsetRegression {
group_id: u64,
expected: u64,
actual: u64,
},
#[error("partial snapshot file corrupt for group {group_id}: {detail}")]
PartialSnapshotCorrupt { group_id: u64, detail: String },
#[error("partial snapshot cleanup failed for group {group_id}: {detail}")]
PartialSnapshotCleanupFailed { group_id: u64, detail: String },
}