use core::fmt;
use error_forge::ForgeError;
use crate::types::NodeId;
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
NotLeader {
leader: Option<NodeId>,
},
Storage {
context: &'static str,
detail: String,
},
Encoding {
context: &'static str,
detail: String,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotLeader { leader: Some(id) } => {
write!(f, "not the leader; current leader is node {id}")
}
Self::NotLeader { leader: None } => {
write!(f, "not the leader; no leader is currently known")
}
Self::Storage { context, detail } => {
write!(f, "log storage error while {context}: {detail}")
}
Self::Encoding { context, detail } => {
write!(f, "message framing error while {context}: {detail}")
}
}
}
}
impl std::error::Error for Error {}
impl ForgeError for Error {
fn kind(&self) -> &'static str {
match self {
Self::NotLeader { .. } => "NotLeader",
Self::Storage { .. } => "Storage",
Self::Encoding { .. } => "Encoding",
}
}
fn caption(&self) -> &'static str {
match self {
Self::NotLeader { .. } => "Not the leader",
Self::Storage { .. } => "Log storage failure",
Self::Encoding { .. } => "Message framing failure",
}
}
fn is_retryable(&self) -> bool {
matches!(self, Self::NotLeader { .. })
}
fn is_fatal(&self) -> bool {
matches!(self, Self::Storage { .. })
}
}
impl Error {
#[must_use]
pub fn storage(context: &'static str, source: impl fmt::Display) -> Self {
Self::Storage {
context,
detail: source.to_string(),
}
}
#[must_use]
pub fn encoding(context: &'static str, source: impl fmt::Display) -> Self {
Self::Encoding {
context,
detail: source.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_not_leader_display_with_known_leader() {
let e = Error::NotLeader { leader: Some(7) };
assert_eq!(e.to_string(), "not the leader; current leader is node 7");
}
#[test]
fn test_not_leader_display_without_leader() {
let e = Error::NotLeader { leader: None };
assert_eq!(
e.to_string(),
"not the leader; no leader is currently known"
);
}
#[test]
fn test_storage_constructor_captures_detail() {
let e = Error::storage("sync log", "device busy");
assert_eq!(
e.to_string(),
"log storage error while sync log: device busy"
);
}
#[test]
fn test_forge_metadata_distinguishes_variants() {
let not_leader = Error::NotLeader { leader: None };
let storage = Error::storage("append entries", "x");
assert_eq!(not_leader.kind(), "NotLeader");
assert_eq!(storage.kind(), "Storage");
assert!(not_leader.is_retryable());
assert!(!not_leader.is_fatal());
assert!(!storage.is_retryable());
assert!(storage.is_fatal());
}
}