1use core::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[repr(u8)]
25pub enum Severity {
26 Error = b'E',
28 Warning = b'W',
30 Critical = b'C',
32 Blocked = b'B',
34 Help = b'H',
36 Success = b'S',
38 Completed = b'K',
40 Info = b'I',
42 Trace = b'T',
44}
45
46impl Severity {
47 pub const fn as_char(self) -> char {
49 self as u8 as char
50 }
51
52 pub const fn as_str(self) -> &'static str {
54 match self {
55 Severity::Error => "Error",
56 Severity::Warning => "Warning",
57 Severity::Critical => "Critical",
58 Severity::Blocked => "Blocked",
59 Severity::Help => "Help",
60 Severity::Success => "Success",
61 Severity::Completed => "Completed",
62 Severity::Info => "Info",
63 Severity::Trace => "Trace",
64 }
65 }
66
67 pub const fn description(self) -> &'static str {
69 match self {
70 Severity::Error => "Operation failed",
71 Severity::Warning => "Potential issue or caveat",
72 Severity::Critical => "Severe issue requiring attention",
73 Severity::Blocked => "Execution blocked or waiting",
74 Severity::Help => "Helpful suggestion or recommendation",
75 Severity::Success => "Operation succeeded",
76 Severity::Completed => "Task or phase completed",
77 Severity::Info => "General informational events",
78 Severity::Trace => "Execution traces and instrumentation",
79 }
80 }
81
82 pub const fn priority(self) -> u8 {
91 match self {
92 Severity::Trace => 0,
93 Severity::Info => 1,
94 Severity::Completed => 2,
95 Severity::Success => 3,
96 Severity::Help => 4,
97 Severity::Warning => 5,
98 Severity::Critical => 6,
99 Severity::Blocked => 7,
100 Severity::Error => 8,
101 }
102 }
103
104 pub const fn is_blocking(self) -> bool {
109 matches!(self, Severity::Error | Severity::Blocked)
110 }
111
112 pub const fn is_positive(self) -> bool {
114 matches!(self, Severity::Success | Severity::Completed)
115 }
116
117 pub const fn is_negative(self) -> bool {
119 matches!(
120 self,
121 Severity::Error | Severity::Warning | Severity::Critical | Severity::Blocked
122 )
123 }
124
125 pub const fn is_neutral(self) -> bool {
127 matches!(self, Severity::Info | Severity::Trace)
128 }
129
130 #[cfg(feature = "emoji")]
157 pub const fn emoji(self) -> &'static str {
158 match self {
159 Severity::Error => "â",
160 Severity::Blocked => "đĢ",
161 Severity::Critical => "đĨ",
162 Severity::Warning => "â ī¸",
163 Severity::Help => "đĄ",
164 Severity::Success => "â
",
165 Severity::Completed => "âī¸",
166 Severity::Info => "âšī¸",
167 Severity::Trace => "đ",
168 }
169 }
170
171 #[cfg(feature = "ansi-colors")]
187 pub const fn ansi_color(self) -> &'static str {
188 match self {
189 Severity::Error => "\x1b[1;31m", Severity::Blocked => "\x1b[31m", Severity::Critical => "\x1b[1;33m", Severity::Warning => "\x1b[33m", Severity::Help => "\x1b[32m", Severity::Success => "\x1b[1;32m", Severity::Completed => "\x1b[32m", Severity::Info => "\x1b[36m", Severity::Trace => "\x1b[2;34m", }
199 }
200
201 #[cfg(feature = "ansi-colors")]
203 pub const ANSI_RESET: &'static str = "\x1b[0m";
204
205 pub const fn hex_color(self) -> &'static str {
221 match self {
222 Severity::Error => "#c0392b",
223 Severity::Blocked => "#e74c3c",
224 Severity::Critical => "#e67e22",
225 Severity::Warning => "#f39c12",
226 Severity::Help => "#27ae60",
227 Severity::Success => "#2ecc71",
228 Severity::Completed => "#16a085",
229 Severity::Info => "#3498db",
230 Severity::Trace => "#95a5a6",
231 }
232 }
233
234 pub const fn hex_bg_color(self) -> &'static str {
238 match self {
239 Severity::Error => "#fee",
240 Severity::Blocked => "#fdd",
241 Severity::Critical => "#ffe5cc",
242 Severity::Warning => "#fef3cd",
243 Severity::Help => "#d4edda",
244 Severity::Success => "#dcfce7", Severity::Completed => "#d1f2eb", Severity::Info => "#d1ecf1",
247 Severity::Trace => "#e2e3e5",
248 }
249 }
250}
251
252impl fmt::Display for Severity {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 write!(f, "{}", self.as_char())
255 }
256}
257
258impl PartialOrd for Severity {
259 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
260 Some(self.cmp(other))
261 }
262}
263
264impl Ord for Severity {
265 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
266 self.priority().cmp(&other.priority())
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_severity_ordering() {
276 assert!(Severity::Trace.priority() < Severity::Error.priority());
277 assert!(Severity::Warning.priority() < Severity::Critical.priority());
278 }
279
280 #[test]
281 fn test_severity_comparison() {
282 assert!(Severity::Trace < Severity::Error);
283 assert!(Severity::Info < Severity::Warning);
284 assert!(Severity::Warning < Severity::Critical);
285 assert!(Severity::Critical < Severity::Blocked);
286 assert!(Severity::Blocked < Severity::Error);
287 }
288
289 #[test]
290 fn test_severity_blocking() {
291 assert!(Severity::Error.is_blocking());
293 assert!(Severity::Blocked.is_blocking());
294
295 assert!(!Severity::Critical.is_blocking());
297 assert!(!Severity::Warning.is_blocking());
298 assert!(!Severity::Success.is_blocking());
299 assert!(!Severity::Completed.is_blocking());
300 assert!(!Severity::Info.is_blocking());
301 assert!(!Severity::Trace.is_blocking());
302 }
303
304 #[test]
305 fn test_severity_categorization() {
306 assert!(Severity::Success.is_positive());
308 assert!(Severity::Completed.is_positive());
309
310 assert!(Severity::Error.is_negative());
312 assert!(Severity::Warning.is_negative());
313 assert!(Severity::Critical.is_negative());
314 assert!(Severity::Blocked.is_negative());
315
316 assert!(Severity::Info.is_neutral());
318 assert!(Severity::Trace.is_neutral());
319
320 assert!(!Severity::Error.is_positive());
322 assert!(!Severity::Success.is_negative());
323 assert!(!Severity::Info.is_positive());
324 assert!(!Severity::Info.is_negative());
325 }
326
327 #[test]
328 fn test_severity_metadata() {
329 assert_eq!(Severity::Error.as_str(), "Error");
330 assert_eq!(Severity::Warning.as_str(), "Warning");
331 assert_eq!(Severity::Critical.as_str(), "Critical");
332
333 assert_eq!(Severity::Error.description(), "Operation failed");
334 assert_eq!(Severity::Warning.description(), "Potential issue or caveat");
335
336 assert_eq!(Severity::Error.as_char(), 'E');
337 assert_eq!(Severity::Warning.as_char(), 'W');
338 }
339
340 #[test]
341 fn test_severity_positive() {
342 assert!(Severity::Success.is_positive());
343 assert!(Severity::Completed.is_positive());
344 assert!(!Severity::Error.is_positive());
345 assert!(!Severity::Warning.is_positive());
346 assert!(!Severity::Critical.is_positive());
347 assert!(!Severity::Blocked.is_positive());
348 assert!(!Severity::Info.is_positive());
349 assert!(!Severity::Trace.is_positive());
350 }
351
352 #[cfg(feature = "emoji")]
353 #[test]
354 fn test_emoji() {
355 assert_eq!(Severity::Error.emoji(), "â");
356 assert_eq!(Severity::Warning.emoji(), "â ī¸");
357 assert_eq!(Severity::Critical.emoji(), "đĨ");
358 assert_eq!(Severity::Success.emoji(), "â
");
359 assert_eq!(Severity::Completed.emoji(), "âī¸");
360 assert_eq!(Severity::Info.emoji(), "âšī¸");
361 assert_eq!(Severity::Trace.emoji(), "đ");
362 assert_eq!(Severity::Blocked.emoji(), "đĢ");
363 }
364
365 #[cfg(feature = "ansi-colors")]
366 #[test]
367 fn test_ansi_colors() {
368 assert_eq!(Severity::Error.ansi_color(), "\x1b[1;31m");
369 assert_eq!(Severity::Warning.ansi_color(), "\x1b[33m");
370 assert_eq!(Severity::Success.ansi_color(), "\x1b[1;32m");
371 assert_eq!(Severity::ANSI_RESET, "\x1b[0m");
372 }
373
374 #[cfg(feature = "serde")]
375 #[test]
376 fn test_serde() {
377 use serde_json;
378
379 let severity = Severity::Error;
380 let json = serde_json::to_string(&severity).unwrap();
381 let deserialized: Severity = serde_json::from_str(&json).unwrap();
382 assert_eq!(severity, deserialized);
383 }
384}