1#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
12pub enum Severity {
13 Debug,
15 #[default]
17 Info,
18 Warn,
20 Error,
22 Critical,
24}
25
26impl Severity {
27 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub enum ErrorSource {
58 Command,
60 Runtime,
62 Subscription,
64 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#[derive(Clone, Debug)]
99pub struct FrameworkError {
100 pub severity: Severity,
102 pub source: ErrorSource,
104 pub message: String,
106 pub context: Option<String>,
108}
109
110impl FrameworkError {
111 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 pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
123 self.context = Some(ctx.into());
124 self
125 }
126
127 pub fn command(severity: Severity, message: impl Into<String>) -> Self {
129 Self::new(severity, ErrorSource::Command, message)
130 }
131
132 pub fn runtime(severity: Severity, message: impl Into<String>) -> Self {
134 Self::new(severity, ErrorSource::Runtime, message)
135 }
136
137 pub fn subscription(severity: Severity, message: impl Into<String>) -> Self {
139 Self::new(severity, ErrorSource::Subscription, message)
140 }
141
142 pub fn view(severity: Severity, message: impl Into<String>) -> Self {
144 Self::new(severity, ErrorSource::View, message)
145 }
146
147 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 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}