open_wal/error.rs
1//! Error type for the WAL.
2//!
3//! `WalError` is a non-panicking enum (§10 of `docs/wal_design_v6.md`). The
4//! recovery parser MUST return these errors, never panic, for all inputs (D11).
5
6use std::fmt;
7
8use crate::Lsn;
9
10/// Convenience alias for results returned by this crate.
11pub type Result<T> = std::result::Result<T, WalError>;
12
13/// All error conditions surfaced by the WAL.
14///
15/// This is the normative `WalError` shape from §10. Variants beyond `Io` carry
16/// enough context (segment base LSN, byte offset) to locate the fault.
17#[derive(Debug)]
18#[non_exhaustive]
19pub enum WalError {
20 /// An underlying I/O error that is not itself a durability failure.
21 Io(std::io::Error),
22
23 /// A record or header failed CRC / structural validation mid-segment.
24 Corruption {
25 /// `base_lsn` of the segment containing the fault.
26 segment: Lsn,
27 /// Byte offset within the segment where the fault was detected.
28 offset: u64,
29 /// Short, static description of what was wrong.
30 detail: &'static str,
31 },
32
33 /// A valid record exists *after* a bad/torn one — a non-truncatable
34 /// internal gap. Fatal and loud, never silently truncated (D5).
35 TornMidLog {
36 /// `base_lsn` of the segment containing the fault.
37 segment: Lsn,
38 /// Byte offset within the segment of the torn record.
39 offset: u64,
40 },
41
42 /// An `append` payload exceeds `max_record_size` (no silent truncation,
43 /// no fragmentation in v1).
44 RecordTooLarge,
45
46 /// The supplied [`WalConfig`](crate::WalConfig) is invalid (e.g.
47 /// `max_record_size > segment_size - 91`, §5.3).
48 InvalidConfig,
49
50 /// A segment header has bad `magic` or `header_crc` (§5.2). The header is
51 /// written and synced at creation, so it is never a torn tail — always
52 /// fatal.
53 BadSegmentHeader,
54
55 /// The directory's exclusive writer lock is already held.
56 Locked,
57
58 /// A `write`/`fdatasync`/`fsync` failed. This **poisons** the handle
59 /// (§12); there is no safe resume for a dense-LSN log.
60 FsyncFailed,
61
62 /// Retained segments do not form a contiguous LSN suffix (§8.1).
63 ContiguityViolation,
64
65 /// The handle was poisoned by a prior durability failure (§12). All
66 /// subsequent `append`/`commit` calls return this.
67 Poisoned,
68}
69
70impl fmt::Display for WalError {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 WalError::Io(e) => write!(f, "I/O error: {e}"),
74 WalError::Corruption {
75 segment,
76 offset,
77 detail,
78 } => write!(
79 f,
80 "corruption in segment {segment} at offset {offset}: {detail}"
81 ),
82 WalError::TornMidLog { segment, offset } => {
83 write!(
84 f,
85 "torn record mid-log in segment {segment} at offset {offset}"
86 )
87 }
88 WalError::RecordTooLarge => write!(f, "record exceeds max_record_size"),
89 WalError::InvalidConfig => write!(f, "invalid WAL configuration"),
90 WalError::BadSegmentHeader => write!(f, "bad segment header"),
91 WalError::Locked => write!(f, "WAL directory is already locked by another writer"),
92 WalError::FsyncFailed => write!(f, "fsync failed; handle is poisoned"),
93 WalError::ContiguityViolation => write!(f, "retained segments are not contiguous"),
94 WalError::Poisoned => write!(f, "WAL handle is poisoned by a prior durability failure"),
95 }
96 }
97}
98
99impl std::error::Error for WalError {
100 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
101 match self {
102 WalError::Io(e) => Some(e),
103 _ => None,
104 }
105 }
106}
107
108impl From<std::io::Error> for WalError {
109 fn from(e: std::io::Error) -> Self {
110 WalError::Io(e)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn io_error_source_is_preserved() {
120 let io = std::io::Error::other("boom");
121 let err = WalError::from(io);
122 assert!(std::error::Error::source(&err).is_some());
123 }
124
125 #[test]
126 fn display_includes_location_context() {
127 let err = WalError::Corruption {
128 segment: Lsn(100001),
129 offset: 84,
130 detail: "crc mismatch",
131 };
132 let s = err.to_string();
133 assert!(s.contains("100001"));
134 assert!(s.contains("84"));
135 assert!(s.contains("crc mismatch"));
136 }
137}