waddling_errors/
severity.rs

1//! Severity levels for diagnostic codes
2
3use core::fmt;
4
5/// Diagnostic severity level (single-character prefix for 4-part codes)
6///
7/// Each severity gets a single-character prefix: `SEVERITY.COMPONENT.PRIMARY.SEQUENCE`
8///
9/// # Available Severities
10///
11/// | Severity | Char | Priority | Meaning |
12/// |----------|------|----------|---------|
13/// | Error | E | 8 | Operation failed |
14/// | Blocked | B | 7 | Execution blocked/waiting |
15/// | Critical | C | 6 | Severe issue requiring attention |
16/// | Warning | W | 5 | Potential issue |
17/// | Help | H | 4 | Helpful suggestion |
18/// | Success | S | 3 | Operation succeeded |
19/// | Completed | K | 2 | Task/phase completed |
20/// | Info | I | 1 | Informational events |
21/// | Trace | T | 0 | Execution traces |
22#[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 (E)** - Operation failed
27    Error = b'E',
28    /// **Warning (W)** - Potential issue or caveat
29    Warning = b'W',
30    /// **Critical (C)** - Severe issue requiring attention
31    Critical = b'C',
32    /// **Blocked (B)** - Execution blocked/waiting
33    Blocked = b'B',
34    /// **Help (H)** - Helpful suggestion or recommendation
35    Help = b'H',
36    /// **Success (S)** - Operation succeeded
37    Success = b'S',
38    /// **Completed (K)** - Task or phase completed
39    Completed = b'K',
40    /// **Info (I)** - General informational events
41    Info = b'I',
42    /// **Trace (T)** - Execution traces and instrumentation
43    Trace = b'T',
44}
45
46impl Severity {
47    /// Get the single-character code (E, W, C, B, S, K, I, T)
48    pub const fn as_char(self) -> char {
49        self as u8 as char
50    }
51
52    /// Get the full name as a string slice (e.g., "Error", "Warning")
53    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    /// Get a human-readable description of this severity level
68    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    /// Get the priority level (0-8, higher = more severe)
83    ///
84    /// **Priority Scale:**
85    /// - 0-1: Diagnostic (Trace, Info)
86    /// - 2-3: Positive (Completed, Success)
87    /// - 4: Suggestions (Help)
88    /// - 5-6: Issues (Warning, Critical)
89    /// - 7-8: Blocking (Blocked, Error)
90    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    /// Check if this severity **blocks execution**
105    ///
106    /// Returns `true` only for Error and Blocked.
107    /// Critical is a severe warning but does NOT block.
108    pub const fn is_blocking(self) -> bool {
109        matches!(self, Severity::Error | Severity::Blocked)
110    }
111
112    /// Check if this is a **positive outcome** (Success/Completed)
113    pub const fn is_positive(self) -> bool {
114        matches!(self, Severity::Success | Severity::Completed)
115    }
116
117    /// Check if this is a **negative outcome** (Error/Warning/Critical/Blocked)
118    pub const fn is_negative(self) -> bool {
119        matches!(
120            self,
121            Severity::Error | Severity::Warning | Severity::Critical | Severity::Blocked
122        )
123    }
124
125    /// Check if this is **neutral** (Info/Trace)
126    pub const fn is_neutral(self) -> bool {
127        matches!(self, Severity::Info | Severity::Trace)
128    }
129
130    /// Get emoji representation for visual display (requires "emoji" feature)
131    ///
132    /// Returns a unicode emoji that visually represents the severity level.
133    /// Perfect for modern terminal UIs, logs, and documentation.
134    ///
135    /// # Emoji Mapping
136    ///
137    /// - Error: ❌ (cross mark)
138    /// - Blocked: đŸšĢ (prohibited)
139    /// - Critical: đŸ”Ĩ (fire - severe issue!)
140    /// - Critical: đŸ”Ĩ (fire - severe issue!)
141    /// - Warning: âš ī¸ (warning sign)
142    /// - Help: 💡 (light bulb - helpful suggestion)
143    /// - Success: ✅ (check mark)
144    /// - Completed: âœ”ī¸ (check mark button)
145    /// - Info: â„šī¸ (information)
146    /// - Trace: 🔍 (magnifying glass)
147    ///
148    /// # Examples
149    ///
150    /// ```rust
151    /// use waddling_errors::Severity;
152    ///
153    /// println!("{} Error occurred!", Severity::Error.emoji());
154    /// println!("{} Build successful!", Severity::Success.emoji());
155    /// ```
156    #[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    /// Get ANSI color code for terminal display (requires "ansi-colors" feature)
172    ///
173    /// Returns the ANSI escape sequence to colorize terminal output.
174    ///
175    /// # Color Scheme
176    ///
177    /// - Error: Red (bold)
178    /// - Blocked: Red
179    /// - Critical: Yellow (bold)
180    /// - Warning: Yellow
181    /// - Help: Green
182    /// - Success: Green (bold)
183    /// - Completed: Green
184    /// - Info: Cyan
185    /// - Trace: Blue (dim)
186    #[cfg(feature = "ansi-colors")]
187    pub const fn ansi_color(self) -> &'static str {
188        match self {
189            Severity::Error => "\x1b[1;31m",    // Bold Red
190            Severity::Blocked => "\x1b[31m",    // Red
191            Severity::Critical => "\x1b[1;33m", // Bold Yellow
192            Severity::Warning => "\x1b[33m",    // Yellow
193            Severity::Help => "\x1b[32m",       // Green
194            Severity::Success => "\x1b[1;32m",  // Bold Green
195            Severity::Completed => "\x1b[32m",  // Green
196            Severity::Info => "\x1b[36m",       // Cyan
197            Severity::Trace => "\x1b[2;34m",    // Dim Blue
198        }
199    }
200
201    /// ANSI reset code (requires "ansi-colors" feature)
202    #[cfg(feature = "ansi-colors")]
203    pub const ANSI_RESET: &'static str = "\x1b[0m";
204
205    /// Get hex color for HTML/CSS display
206    ///
207    /// Returns a CSS-compatible hex color code for web UIs.
208    ///
209    /// # Color Scheme
210    ///
211    /// - Error: #c0392b (dark red)
212    /// - Blocked: #e74c3c (red)
213    /// - Critical: #e67e22 (orange)
214    /// - Warning: #f39c12 (yellow/orange)
215    /// - Help: #27ae60 (green)
216    /// - Success: #2ecc71 (bright green)
217    /// - Completed: #16a085 (teal)
218    /// - Info: #3498db (blue)
219    /// - Trace: #95a5a6 (gray)
220    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    /// Get background color for HTML/CSS badges
235    ///
236    /// Returns a light background color suitable for badges/chips.
237    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",   // Light green
245            Severity::Completed => "#d1f2eb", // Light teal
246            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        // Blocking
292        assert!(Severity::Error.is_blocking());
293        assert!(Severity::Blocked.is_blocking());
294
295        // Non-blocking (Critical is a warning!)
296        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        // Positive
307        assert!(Severity::Success.is_positive());
308        assert!(Severity::Completed.is_positive());
309
310        // Negative
311        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        // Neutral
317        assert!(Severity::Info.is_neutral());
318        assert!(Severity::Trace.is_neutral());
319
320        // Mutual exclusivity
321        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}