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> CompositeErrorHandler<M> {
70    /// Create a new composite error handler
71    pub fn new() -> Self {
72        Self {
73            handlers: Vec::new(),
74        }
75    }
76
77    /// Add an error handler to the composite
78    pub fn add_handler(mut self, handler: Box<dyn ErrorHandler<M>>) -> Self {
79        self.handlers.push(handler);
80        self
81    }
82}
83
84impl<M> ErrorHandler<M> for CompositeErrorHandler<M> {
85    fn handle_error(&self, error: Error, tx: &SyncSender<Event<M>>) {
86        // Since Error doesn't implement Clone, we convert to string and recreate
87        let error_string = error.to_string();
88        for handler in &self.handlers {
89            handler.handle_error(Error::Model(error_string.clone()), tx);
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use std::sync::mpsc;
98
99    #[derive(Clone, Debug, PartialEq)]
100    enum TestMsg {
101        Error(String),
102    }
103
104    #[test]
105    fn test_default_error_handler() {
106        let (tx, _rx) = mpsc::sync_channel::<Event<TestMsg>>(10);
107        let handler = DefaultErrorHandler;
108        
109        let error = Error::Model("Test error".to_string());
110        handler.handle_error(error, &tx);
111        
112        // Default handler only logs, doesn't send events
113        // We can't easily test stderr output, but at least verify it doesn't panic
114    }
115
116    #[test]
117    fn test_event_error_handler() {
118        let (tx, rx) = mpsc::sync_channel(10);
119        let handler = EventErrorHandler::new(|err| TestMsg::Error(err.to_string()));
120        
121        let error = Error::Model("Test error".to_string());
122        handler.handle_error(error, &tx);
123        
124        // Should receive error as event
125        let event = rx.recv().unwrap();
126        // The Error::Model variant includes prefix "Model error: " in Display
127        assert_eq!(event, Event::User(TestMsg::Error("Model error: Test error".to_string())));
128    }
129    
130    #[test]
131    fn test_composite_error_handler() {
132        let (tx, rx) = mpsc::sync_channel(10);
133        
134        let composite = CompositeErrorHandler::new()
135            .add_handler(Box::new(DefaultErrorHandler))
136            .add_handler(Box::new(EventErrorHandler::new(|err| {
137                TestMsg::Error(format!("Handled: {}", err))
138            })));
139        
140        let error = Error::Model("Test error".to_string());
141        composite.handle_error(error, &tx);
142        
143        // Should receive event from EventErrorHandler
144        // Note: CompositeErrorHandler converts error to string then wraps as Model error
145        // So "Model error: Test error" becomes "Model error: Model error: Test error"
146        let event = rx.recv().unwrap();
147        assert_eq!(event, Event::User(TestMsg::Error("Handled: Model error: Model error: Test error".to_string())));
148    }
149}