use std::fmt; /// Errors originating from WAL operations. /// /// Covers I/O failures, data corruption detected during recovery, /// and lifecycle violations (e.g., appending after shutdown). #[derive(Debug)] pub enum WalError { /// Underlying filesystem I/O failure. Io(std::io::Error), /// Data corruption detected (BLAKE3 mismatch, invalid magic, etc.). Corruption { message: String }, /// Current segment is full; internal signal to trigger rotation. SegmentFull, /// Attempted append after WAL has been shut down. Closed, /// Channel send to writer thread failed (writer thread panicked or exited). SendFailed, /// Writer thread join failed during shutdown. ShutdownFailed, } impl fmt::Display for WalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Io(source) => write!(f, "WAL I/O error: {source}"), Self::Corruption { message } => write!(f, "WAL corruption: {message}"), Self::SegmentFull => f.write_str("WAL segment full"), Self::Closed => f.write_str("WAL closed"), Self::SendFailed => f.write_str("WAL channel send failed"), Self::ShutdownFailed => f.write_str("WAL shutdown failed"), } } } impl std::error::Error for WalError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Io(source) => Some(source), _ => None, } } } impl From for WalError { fn from(e: std::io::Error) -> Self { Self::Io(e) } } #[cfg(test)] mod tests { use super::*; #[test] fn display_io() { let e = WalError::Io(std::io::Error::new( std::io::ErrorKind::NotFound, "file not found", )); assert!(e.to_string().contains("I/O error")); assert!(e.to_string().contains("file not found")); } #[test] fn display_corruption() { let e = WalError::Corruption { message: "bad checksum".into(), }; assert_eq!(e.to_string(), "WAL corruption: bad checksum"); } #[test] fn display_segment_full() { assert_eq!(WalError::SegmentFull.to_string(), "WAL segment full"); } #[test] fn display_closed() { assert_eq!(WalError::Closed.to_string(), "WAL closed"); } #[test] fn display_send_failed() { assert_eq!(WalError::SendFailed.to_string(), "WAL channel send failed"); } #[test] fn display_shutdown_failed() { assert_eq!(WalError::ShutdownFailed.to_string(), "WAL shutdown failed"); } #[test] fn from_io_error() { let io_err = std::io::Error::new(std::io::ErrorKind::Other, "disk full"); let wal_err: WalError = io_err.into(); assert!(matches!(wal_err, WalError::Io(_))); } #[test] fn source_io() { use std::error::Error; let e = WalError::Io(std::io::Error::new(std::io::ErrorKind::Other, "test")); assert!(e.source().is_some()); } #[test] fn source_corruption_is_none() { use std::error::Error; let e = WalError::Corruption { message: "test".into(), }; assert!(e.source().is_none()); } #[test] fn source_closed_is_none() { use std::error::Error; assert!(WalError::Closed.source().is_none()); } }