nmbrs_errorhandler/
handlers.rs1use std::sync::atomic::AtomicPtr;
7
8static LOG_FN: AtomicPtr<()> = AtomicPtr::new(default_log as *mut ());
12
13type LogFn = fn(&str);
14
15fn default_log(msg: &str) {
16 eprintln!("{msg}");
17}
18
19pub fn set_log_fn(f: LogFn) {
21 LOG_FN.store(f as *mut (), std::sync::atomic::Ordering::Release);
22}
23
24fn emit(msg: &str) {
25 let ptr = LOG_FN.load(std::sync::atomic::Ordering::Acquire);
26 let f: LogFn = unsafe { std::mem::transmute(ptr) };
27 f(msg);
28}
29
30use std::collections::HashMap;
31use std::sync::Mutex;
32use std::sync::atomic::{AtomicU64, Ordering};
33
34use crate::detail::ErrorDetail;
35use crate::handler::ErrorHandler;
36
37pub struct StopHandler;
40
41impl ErrorHandler for StopHandler {
42 fn handle(
43 &self,
44 _name: &str,
45 _error_msg: &str,
46 _cycle: u64,
47 _duration_nanos: u64,
48 detail: ErrorDetail,
49 ) -> ErrorDetail {
50 detail.with_stop()
51 }
52}
53
54pub struct WarnHandler;
56
57impl ErrorHandler for WarnHandler {
58 fn handle(
59 &self,
60 name: &str,
61 error_msg: &str,
62 cycle: u64,
63 _duration_nanos: u64,
64 detail: ErrorDetail,
65 ) -> ErrorDetail {
66 emit(&format!(
67 "WARN error at cycle {cycle}: [{name}] {error_msg}"
68 ));
69 detail
70 }
71}
72
73pub struct ErrorLogHandler;
75
76impl ErrorHandler for ErrorLogHandler {
77 fn handle(
78 &self,
79 name: &str,
80 error_msg: &str,
81 cycle: u64,
82 _duration_nanos: u64,
83 detail: ErrorDetail,
84 ) -> ErrorDetail {
85 emit(&format!("ERROR at cycle {cycle}: [{name}] {error_msg}"));
86 detail
87 }
88}
89
90pub struct IgnoreHandler;
92
93impl ErrorHandler for IgnoreHandler {
94 fn handle(
95 &self,
96 _name: &str,
97 _error_msg: &str,
98 _cycle: u64,
99 _duration_nanos: u64,
100 detail: ErrorDetail,
101 ) -> ErrorDetail {
102 detail
103 }
104}
105
106pub struct RetryHandler;
108
109impl ErrorHandler for RetryHandler {
110 fn handle(
111 &self,
112 _name: &str,
113 _error_msg: &str,
114 _cycle: u64,
115 _duration_nanos: u64,
116 detail: ErrorDetail,
117 ) -> ErrorDetail {
118 detail.with_retryable()
119 }
120}
121
122pub struct CounterHandler {
124 counts: Mutex<HashMap<String, AtomicU64>>,
125}
126
127impl Default for CounterHandler {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133impl CounterHandler {
134 pub fn new() -> Self {
135 Self {
136 counts: Mutex::new(HashMap::new()),
137 }
138 }
139
140 #[allow(dead_code)]
142 pub fn get_count(&self, name: &str) -> u64 {
143 let counts = self.counts.lock().unwrap_or_else(|e| e.into_inner());
144 counts
145 .get(name)
146 .map(|c| c.load(Ordering::Relaxed))
147 .unwrap_or(0)
148 }
149
150 #[allow(dead_code)]
152 pub fn all_counts(&self) -> HashMap<String, u64> {
153 let counts = self.counts.lock().unwrap_or_else(|e| e.into_inner());
154 counts
155 .iter()
156 .map(|(k, v)| (k.clone(), v.load(Ordering::Relaxed)))
157 .collect()
158 }
159}
160
161impl ErrorHandler for CounterHandler {
162 fn handle(
163 &self,
164 name: &str,
165 _error_msg: &str,
166 _cycle: u64,
167 _duration_nanos: u64,
168 detail: ErrorDetail,
169 ) -> ErrorDetail {
170 let mut counts = self.counts.lock().unwrap_or_else(|e| e.into_inner());
171 counts
172 .entry(name.to_string())
173 .or_insert_with(|| AtomicU64::new(0))
174 .fetch_add(1, Ordering::Relaxed);
175 detail
176 }
177}
178
179pub fn builtin_handler(name: &str) -> Option<Box<dyn ErrorHandler>> {
181 match name {
182 "stop" => Some(Box::new(StopHandler)),
183 "warn" => Some(Box::new(WarnHandler)),
184 "error" => Some(Box::new(ErrorLogHandler)),
185 "ignore" => Some(Box::new(IgnoreHandler)),
186 "retry" => Some(Box::new(RetryHandler)),
187 "counter" | "count" => Some(Box::new(CounterHandler::new())),
188 _ => None,
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn retry_handler_sets_retryable() {
198 let h = RetryHandler;
199 let detail = ErrorDetail::non_retryable("test");
200 let result = h.handle("test", "msg", 0, 0, detail);
201 assert!(result.is_retryable());
202 }
203
204 #[test]
205 fn ignore_handler_passes_through() {
206 let h = IgnoreHandler;
207 let detail = ErrorDetail::non_retryable("test");
208 let result = h.handle("test", "msg", 0, 0, detail);
209 assert!(!result.is_retryable());
210 assert_eq!(result.result_code, 127);
211 }
212
213 #[test]
214 fn counter_handler_counts() {
215 let h = CounterHandler::new();
216 let detail = ErrorDetail::non_retryable("TimeoutError");
217 h.handle("TimeoutError", "timed out", 1, 0, detail.clone());
218 h.handle("TimeoutError", "timed out", 2, 0, detail.clone());
219 h.handle("OtherError", "other", 3, 0, detail);
220 assert_eq!(h.get_count("TimeoutError"), 2);
221 assert_eq!(h.get_count("OtherError"), 1);
222 assert_eq!(h.get_count("Missing"), 0);
223 }
224
225 #[test]
226 fn stop_handler_sets_should_stop() {
227 let h = StopHandler;
228 let detail = ErrorDetail::non_retryable("test");
229 let result = h.handle("test", "boom", 42, 0, detail);
230 assert!(
231 result.should_stop,
232 "stop handler should set should_stop flag"
233 );
234 }
235
236 #[test]
237 fn builtin_lookup() {
238 assert!(builtin_handler("stop").is_some());
239 assert!(builtin_handler("warn").is_some());
240 assert!(builtin_handler("error").is_some());
241 assert!(builtin_handler("ignore").is_some());
242 assert!(builtin_handler("retry").is_some());
243 assert!(builtin_handler("counter").is_some());
244 assert!(builtin_handler("count").is_some());
245 assert!(builtin_handler("bogus").is_none());
246 }
247}