Skip to main content

egui_cha/
error.rs

1//! Error handling types for egui-cha framework
2//!
3//! Provides unified error handling with:
4//! - [`Severity`] - Unified severity levels compatible with tracing
5//! - [`FrameworkError`] - Errors from framework internals
6//! - [`ErrorSource`] - Categorization of error origins
7
8/// Unified severity level for both tracing and ErrorChannel
9///
10/// Ordered from least to most severe for comparison operations.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
12pub enum Severity {
13    /// Development debugging (shown only in debug mode)
14    Debug,
15    /// Informational message
16    #[default]
17    Info,
18    /// Warning (operation continues)
19    Warn,
20    /// Error (recoverable)
21    Error,
22    /// Critical error (may require restart)
23    Critical,
24}
25
26impl Severity {
27    /// Convert to tracing::Level
28    pub fn to_tracing_level(self) -> tracing::Level {
29        match self {
30            Severity::Debug => tracing::Level::DEBUG,
31            Severity::Info => tracing::Level::INFO,
32            Severity::Warn => tracing::Level::WARN,
33            Severity::Error | Severity::Critical => tracing::Level::ERROR,
34        }
35    }
36
37    /// Check if this severity should be shown in production
38    pub fn is_production_visible(self) -> bool {
39        self >= Severity::Info
40    }
41}
42
43impl std::fmt::Display for Severity {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            Severity::Debug => write!(f, "DEBUG"),
47            Severity::Info => write!(f, "INFO"),
48            Severity::Warn => write!(f, "WARN"),
49            Severity::Error => write!(f, "ERROR"),
50            Severity::Critical => write!(f, "CRITICAL"),
51        }
52    }
53}
54
55/// Where the error originated within the framework
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub enum ErrorSource {
58    /// Command execution (Cmd::Task failed, panic, etc.)
59    Command,
60    /// Runtime internals (tokio, channel, etc.)
61    Runtime,
62    /// Subscription handling
63    Subscription,
64    /// View rendering
65    View,
66}
67
68impl std::fmt::Display for ErrorSource {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            ErrorSource::Command => write!(f, "Command"),
72            ErrorSource::Runtime => write!(f, "Runtime"),
73            ErrorSource::Subscription => write!(f, "Subscription"),
74            ErrorSource::View => write!(f, "View"),
75        }
76    }
77}
78
79/// Error from egui-cha framework internals
80///
81/// This type represents errors that occur within the framework itself,
82/// such as task panics, runtime failures, or subscription errors.
83///
84/// # Example
85/// ```ignore
86/// fn on_framework_error(model: &mut Model, err: FrameworkError) -> Cmd<Msg> {
87///     // Add to your error console
88///     model.errors.push_with_level(&err.message, err.severity.into());
89///
90///     // Or handle specific sources differently
91///     match err.source {
92///         ErrorSource::Command => { /* retry logic */ }
93///         _ => { /* log and continue */ }
94///     }
95///     Cmd::none()
96/// }
97/// ```
98#[derive(Clone, Debug)]
99pub struct FrameworkError {
100    /// Error severity
101    pub severity: Severity,
102    /// Error source/category
103    pub source: ErrorSource,
104    /// Error message
105    pub message: String,
106    /// Optional context (additional details)
107    pub context: Option<String>,
108}
109
110impl FrameworkError {
111    /// Create a new framework error
112    pub fn new(severity: Severity, source: ErrorSource, message: impl Into<String>) -> Self {
113        Self {
114            severity,
115            source,
116            message: message.into(),
117            context: None,
118        }
119    }
120
121    /// Add context to the error
122    pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
123        self.context = Some(ctx.into());
124        self
125    }
126
127    /// Create a command error
128    pub fn command(severity: Severity, message: impl Into<String>) -> Self {
129        Self::new(severity, ErrorSource::Command, message)
130    }
131
132    /// Create a runtime error
133    pub fn runtime(severity: Severity, message: impl Into<String>) -> Self {
134        Self::new(severity, ErrorSource::Runtime, message)
135    }
136
137    /// Create a subscription error
138    pub fn subscription(severity: Severity, message: impl Into<String>) -> Self {
139        Self::new(severity, ErrorSource::Subscription, message)
140    }
141
142    /// Create a view error
143    pub fn view(severity: Severity, message: impl Into<String>) -> Self {
144        Self::new(severity, ErrorSource::View, message)
145    }
146
147    /// Log this error to tracing
148    pub fn log(&self) {
149        let msg = self.format_message();
150
151        match self.severity {
152            Severity::Debug => tracing::debug!("{}", msg),
153            Severity::Info => tracing::info!("{}", msg),
154            Severity::Warn => tracing::warn!("{}", msg),
155            Severity::Error => tracing::error!("{}", msg),
156            Severity::Critical => tracing::error!("[CRITICAL] {}", msg),
157        }
158    }
159
160    /// Format the error message with source and context
161    pub fn format_message(&self) -> String {
162        match &self.context {
163            Some(ctx) => format!("[{}] {} ({})", self.source, self.message, ctx),
164            None => format!("[{}] {}", self.source, self.message),
165        }
166    }
167}
168
169impl std::fmt::Display for FrameworkError {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        write!(f, "{}", self.format_message())
172    }
173}
174
175impl std::error::Error for FrameworkError {}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn severity_ordering() {
183        assert!(Severity::Debug < Severity::Info);
184        assert!(Severity::Info < Severity::Warn);
185        assert!(Severity::Warn < Severity::Error);
186        assert!(Severity::Error < Severity::Critical);
187    }
188
189    #[test]
190    fn severity_production_visibility() {
191        assert!(!Severity::Debug.is_production_visible());
192        assert!(Severity::Info.is_production_visible());
193        assert!(Severity::Warn.is_production_visible());
194        assert!(Severity::Error.is_production_visible());
195        assert!(Severity::Critical.is_production_visible());
196    }
197
198    #[test]
199    fn framework_error_formatting() {
200        let err = FrameworkError::command(Severity::Error, "Task failed");
201        assert_eq!(err.format_message(), "[Command] Task failed");
202
203        let err_with_ctx = err.with_context("user_id=123");
204        assert_eq!(
205            err_with_ctx.format_message(),
206            "[Command] Task failed (user_id=123)"
207        );
208    }
209}