1use std::fmt;
18use crate::tock::Tock;
19
20#[derive(Debug)]
25pub enum ZombieError {
26 ContextNotFound {
29 tock: Tock,
30 location: &'static str,
32 },
33
34 NoReplayer {
36 context_start: Tock,
37 context_end: Tock,
38 },
39
40 ReplayFailed {
42 target: Tock,
43 reason: Box<str>,
44 },
45
46 TypeMismatch {
48 expected: &'static str,
49 tock: Tock,
50 },
51
52 NotInitialized,
54
55 InvalidRecordState {
57 from: &'static str,
58 to: &'static str,
59 },
60
61 EvictionFailed {
63 reason: Box<str>,
64 },
65
66 IndexOutOfBounds {
68 tock: Tock,
69 context_start: Tock,
70 context_end: Tock,
71 index: usize,
72 len: usize,
73 },
74
75 ReplayLoopExceeded {
77 target: Tock,
78 max_iterations: usize,
79 },
80}
81
82impl fmt::Display for ZombieError {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 match self {
85 ZombieError::ContextNotFound { tock, location } => {
86 write!(f, "no context found for tock {} at {}", tock, location)
87 }
88 ZombieError::NoReplayer { context_start, context_end } => {
89 write!(f, "context [{}, {}) has no replayer", context_start, context_end)
90 }
91 ZombieError::ReplayFailed { target, reason } => {
92 write!(f, "replay to tock {} failed: {}", target, reason)
93 }
94 ZombieError::TypeMismatch { expected, tock } => {
95 write!(f, "type mismatch at tock {}, expected {}", tock, expected)
96 }
97 ZombieError::NotInitialized => {
98 write!(f, "Runtime not initialized, call Runtime::init() first")
99 }
100 ZombieError::InvalidRecordState { from, to } => {
101 write!(f, "invalid record state transition from {} to {}", from, to)
102 }
103 ZombieError::EvictionFailed { reason } => {
104 write!(f, "eviction failed: {}", reason)
105 }
106 ZombieError::IndexOutOfBounds { tock, context_start, context_end, index, len } => {
107 write!(f, "index {} out of bounds (len {}) for tock {} in context [{}, {})",
108 index, len, tock, context_start, context_end)
109 }
110 ZombieError::ReplayLoopExceeded { target, max_iterations } => {
111 write!(f, "replay loop exceeded {} iterations for tock {}", max_iterations, target)
112 }
113 }
114 }
115}
116
117impl std::error::Error for ZombieError {}
118
119pub type ZombieResult<T> = Result<T, ZombieError>;
121
122impl ZombieError {
127 #[cold]
129 #[inline(never)]
130 pub fn context_not_found(tock: Tock, location: &'static str) -> Self {
131 ZombieError::ContextNotFound { tock, location }
132 }
133
134 #[cold]
136 #[inline(never)]
137 pub fn type_mismatch<T>(tock: Tock) -> Self {
138 ZombieError::TypeMismatch {
139 expected: std::any::type_name::<T>(),
140 tock,
141 }
142 }
143
144 #[cold]
146 #[inline(never)]
147 pub fn replay_failed(target: Tock, reason: impl Into<Box<str>>) -> Self {
148 ZombieError::ReplayFailed { target, reason: reason.into() }
149 }
150}
151
152pub trait OptionExt<T> {
158 fn context_not_found(self, tock: Tock, location: &'static str) -> ZombieResult<T>;
160
161 fn no_replayer(self, context_start: Tock, context_end: Tock) -> ZombieResult<T>;
163
164 fn type_mismatch<U>(self, tock: Tock) -> ZombieResult<T>;
166}
167
168impl<T> OptionExt<T> for Option<T> {
169 fn context_not_found(self, tock: Tock, location: &'static str) -> ZombieResult<T> {
170 self.ok_or_else(|| ZombieError::context_not_found(tock, location))
171 }
172
173 fn no_replayer(self, context_start: Tock, context_end: Tock) -> ZombieResult<T> {
174 self.ok_or(ZombieError::NoReplayer { context_start, context_end })
175 }
176
177 fn type_mismatch<U>(self, tock: Tock) -> ZombieResult<T> {
178 self.ok_or_else(|| ZombieError::type_mismatch::<U>(tock))
179 }
180}
181
182pub trait ResultExt<T> {
184 fn with_location(self, location: &'static str) -> ZombieResult<T>;
186}
187
188impl<T> ResultExt<T> for ZombieResult<T> {
189 fn with_location(self, _location: &'static str) -> ZombieResult<T> {
190 self
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_error_display() {
201 let err = ZombieError::context_not_found(Tock(42), "test_location");
202 assert!(err.to_string().contains("42"));
203 assert!(err.to_string().contains("test_location"));
204 }
205
206 #[test]
207 fn test_type_mismatch_includes_type_name() {
208 let err = ZombieError::type_mismatch::<Vec<i32>>(Tock(10));
209 let msg = err.to_string();
210 assert!(msg.contains("Vec"));
211 assert!(msg.contains("10"));
212 }
213
214 #[test]
215 fn test_option_ext() {
216 let none: Option<i32> = None;
217 let result = none.context_not_found(Tock(5), "test");
218 assert!(result.is_err());
219 }
220}