use thiserror::Error;
use nodedb_types::NodeId;
use super::incarnation::Incarnation;
use super::member::MemberState;
#[derive(Debug, Error)]
pub enum SwimError {
#[error("swim: unknown member {node_id}")]
UnknownMember { node_id: NodeId },
#[error("swim: stale incarnation for {node_id}: received {received:?} <= local {local:?}")]
StaleIncarnation {
node_id: NodeId,
received: Incarnation,
local: Incarnation,
},
#[error("swim: local node suspected at incarnation {incarnation:?}")]
SelfSuspected { incarnation: Incarnation },
#[error("swim: invalid state transition {from:?} -> {to:?}")]
InvalidTransition { from: MemberState, to: MemberState },
#[error("swim: invalid config field {field}: {reason}")]
InvalidConfig {
field: &'static str,
reason: &'static str,
},
#[error("swim: encode failure: {detail}")]
Encode { detail: String },
#[error("swim: decode failure: {detail}")]
Decode { detail: String },
#[error("swim: transport closed")]
TransportClosed,
#[error("swim: probe inflight table overflow")]
ProbeInflightOverflow,
}
impl From<SwimError> for crate::error::ClusterError {
fn from(err: SwimError) -> Self {
crate::error::ClusterError::Transport {
detail: err.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_contains_context() {
let err = SwimError::StaleIncarnation {
node_id: NodeId::try_new("n1").expect("test fixture"),
received: Incarnation::new(3),
local: Incarnation::new(5),
};
let msg = err.to_string();
assert!(msg.contains("n1"));
assert!(msg.contains('3'));
assert!(msg.contains('5'));
}
#[test]
fn invalid_config_display() {
let err = SwimError::InvalidConfig {
field: "probe_timeout",
reason: "must be strictly less than probe_interval",
};
assert!(err.to_string().contains("probe_timeout"));
}
#[test]
fn bridges_to_cluster_error() {
let err: crate::error::ClusterError = SwimError::UnknownMember {
node_id: NodeId::try_new("n42").expect("test fixture"),
}
.into();
assert!(matches!(err, crate::error::ClusterError::Transport { .. }));
}
}