hojicha_runtime/program/
error_handler.rs

1//! Error handling utilities for the runtime
2//!
3//! This module provides error handling strategies and utilities for
4//! fallible commands and model operations.
5
6use hojicha_core::error::Error;
7use hojicha_core::event::Event;
8use std::sync::mpsc::SyncSender;
9
10/// Trait for custom error handling strategies
11pub trait ErrorHandler<M>: Send + Sync {
12    /// Handle an error that occurred during command execution
13    fn handle_error(&self, error: Error, tx: &SyncSender<Event<M>>);
14}
15
16/// Default error handler that logs errors to stderr
17pub struct DefaultErrorHandler;
18
19impl<M> ErrorHandler<M> for DefaultErrorHandler {
20    fn handle_error(&self, error: Error, _tx: &SyncSender<Event<M>>) {
21        eprintln!("Command execution error: {}", error);
22
23        // Print error chain
24        let mut current_error: &dyn std::error::Error = &error;
25        while let Some(source) = current_error.source() {
26            eprintln!("  Caused by: {}", source);
27            current_error = source;
28        }
29    }
30}
31
32/// Error handler that converts errors to events
33pub struct EventErrorHandler<M, F>
34where
35    M: Clone + Send + 'static,
36    F: Fn(Error) -> M + Send + Sync,
37{
38    converter: F,
39}
40
41impl<M, F> EventErrorHandler<M, F>
42where
43    M: Clone + Send + 'static,
44    F: Fn(Error) -> M + Send + Sync,
45{
46    /// Create a new event error handler with the given error converter
47    pub fn new(converter: F) -> Self {
48        Self { converter }
49    }
50}
51
52impl<M, F> ErrorHandler<M> for EventErrorHandler<M, F>
53where
54    M: Clone + Send + 'static,
55    F: Fn(Error) -> M + Send + Sync,
56{
57    fn handle_error(&self, error: Error, tx: &SyncSender<Event<M>>) {
58        // Convert error to message and send as event
59        let msg = (self.converter)(error);
60        let _ = tx.send(Event::User(msg));
61    }
62}
63
64/// Error handler that combines logging and event generation
65pub struct CompositeErrorHandler<M> {
66    handlers: Vec<Box<dyn ErrorHandler<M>>>,
67}
68
69impl<M> Default for CompositeErrorHandler<M> {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl<M> CompositeErrorHandler<M> {
76    /// Create a new composite error handler
77    pub fn new() -> Self {
78        Self {
79            handlers: Vec::new(),
80        }
81    }
82
83    /// Add an error handler to the composite
84    pub fn add_handler(mut self, handler: Box<dyn ErrorHandler<M>>) -> Self {
85        self.handlers.push(handler);
86        self
87    }
88}
89
90impl<M> ErrorHandler<M> for CompositeErrorHandler<M> {
91    fn handle_error(&self, error: Error, tx: &SyncSender<Event<M>>) {
92        // Since Error doesn't implement Clone, we convert to string and recreate
93        let error_string = error.to_string();
94        for handler in &self.handlers {
95            handler.handle_error(Error::Model(error_string.clone()), tx);
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use std::sync::mpsc;
104
105    #[derive(Clone, Debug, PartialEq)]
106    enum TestMsg {
107        Error(String),
108    }
109
110    #[test]
111    fn test_default_error_handler() {
112        let (tx, _rx) = mpsc::sync_channel::<Event<TestMsg>>(10);
113        let handler = DefaultErrorHandler;
114
115        let error = Error::Model("Test error".to_string());
116        handler.handle_error(error, &tx);
117
118        // Default handler only logs, doesn't send events
119        // We can't easily test stderr output, but at least verify it doesn't panic
120    }
121
122    #[test]
123    fn test_event_error_handler() {
124        let (tx, rx) = mpsc::sync_channel(10);
125        let handler = EventErrorHandler::new(|err| TestMsg::Error(err.to_string()));
126
127        let error = Error::Model("Test error".to_string());
128        handler.handle_error(error, &tx);
129
130        // Should receive error as event
131        let event = rx.recv().unwrap();
132        // The Error::Model variant includes prefix "Model error: " in Display
133        assert_eq!(
134            event,
135            Event::User(TestMsg::Error("Model error: Test error".to_string()))
136        );
137    }
138
139    #[test]
140    fn test_composite_error_handler() {
141        let (tx, rx) = mpsc::sync_channel(10);
142
143        let composite = CompositeErrorHandler::new()
144            .add_handler(Box::new(DefaultErrorHandler))
145            .add_handler(Box::new(EventErrorHandler::new(|err| {
146                TestMsg::Error(format!("Handled: {}", err))
147            })));
148
149        let error = Error::Model("Test error".to_string());
150        composite.handle_error(error, &tx);
151
152        // Should receive event from EventErrorHandler
153        // Note: CompositeErrorHandler converts error to string then wraps as Model error
154        // So "Model error: Test error" becomes "Model error: Model error: Test error"
155        let event = rx.recv().unwrap();
156        assert_eq!(
157            event,
158            Event::User(TestMsg::Error(
159                "Handled: Model error: Model error: Test error".to_string()
160            ))
161        );
162    }
163}