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 (light mode)
206    ///
207    /// Returns Radix Colors for light backgrounds per WDP Part 10.
208    /// Uses step 11 (text) or step 9 (emphasis) for WCAG AA contrast.
209    ///
210    /// # WDP Part 10 Color Scheme (Radix Colors)
211    ///
212    /// - Error: #ce2c31 (red-11)
213    /// - Blocked: #dc3e42 (red-10)
214    /// - Critical: #cc4e00 (orange-11)
215    /// - Warning: #ab6400 (amber-11)
216    /// - Help: #218358 (green-11)
217    /// - Success: #30a46c (green-9)
218    /// - Completed: #008573 (teal-11)
219    /// - Info: #0d74ce (blue-11)
220    /// - Trace: #60646c (slate-11)
221    pub const fn hex_color(self) -> &'static str {
222        match self {
223            Severity::Error => "#ce2c31",
224            Severity::Blocked => "#dc3e42",
225            Severity::Critical => "#cc4e00",
226            Severity::Warning => "#ab6400",
227            Severity::Help => "#218358",
228            Severity::Success => "#30a46c",
229            Severity::Completed => "#008573",
230            Severity::Info => "#0d74ce",
231            Severity::Trace => "#60646c",
232        }
233    }
234
235    /// Get hex color for HTML/CSS display (dark mode)
236    ///
237    /// Returns Radix Colors dark scale for dark backgrounds per WDP Part 10.
238    /// Perceptually matched with light mode colors for consistency.
239    ///
240    /// # WDP Part 10 Dark Mode Colors (Radix Colors Dark Scale)
241    ///
242    /// - Error: #ff9592 (red-11 dark)
243    /// - Blocked: #ec5d5e (red-10 dark)
244    /// - Critical: #ffa057 (orange-11 dark)
245    /// - Warning: #ffca16 (amber-11 dark)
246    /// - Help: #3dd68c (green-11 dark)
247    /// - Success: #33b074 (green-10 dark)
248    /// - Completed: #0bd8b6 (teal-11 dark)
249    /// - Info: #70b8ff (blue-11 dark)
250    /// - Trace: #b0b4ba (slate-11 dark)
251    pub const fn hex_color_dark(self) -> &'static str {
252        match self {
253            Severity::Error => "#ff9592",
254            Severity::Blocked => "#ec5d5e",
255            Severity::Critical => "#ffa057",
256            Severity::Warning => "#ffca16",
257            Severity::Help => "#3dd68c",
258            Severity::Success => "#33b074",
259            Severity::Completed => "#0bd8b6",
260            Severity::Info => "#70b8ff",
261            Severity::Trace => "#b0b4ba",
262        }
263    }
264
265    /// Get background color for HTML/CSS badges (light mode)
266    ///
267    /// Returns Radix Colors step 3 for light, subtle backgrounds per WDP Part 10.
268    /// Perfect for badges, chips, and callout boxes.
269    pub const fn hex_bg_color(self) -> &'static str {
270        match self {
271            Severity::Error => "#feebec",
272            Severity::Blocked => "#ffdbdc",
273            Severity::Critical => "#ffefd6",
274            Severity::Warning => "#fff7c2",
275            Severity::Help => "#e6f6eb",
276            Severity::Success => "#d6f1df",
277            Severity::Completed => "#e0f8f3",
278            Severity::Info => "#e6f4fe",
279            Severity::Trace => "#f0f0f3",
280        }
281    }
282
283    /// Get background color for HTML/CSS badges (dark mode)
284    ///
285    /// Returns Radix Colors dark scale step 3 for dark, muted backgrounds per WDP Part 10.
286    /// Perfect for badges, chips, and callout boxes in dark themes.
287    pub const fn hex_bg_color_dark(self) -> &'static str {
288        match self {
289            Severity::Error => "#3b1219",
290            Severity::Blocked => "#500f1c",
291            Severity::Critical => "#331e0b",
292            Severity::Warning => "#302008",
293            Severity::Help => "#132d21",
294            Severity::Success => "#113b29",
295            Severity::Completed => "#0d2d2a",
296            Severity::Info => "#0d2847",
297            Severity::Trace => "#212225",
298        }
299    }
300}
301
302impl fmt::Display for Severity {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        write!(f, "{}", self.as_char())
305    }
306}
307
308impl PartialOrd for Severity {
309    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
310        Some(self.cmp(other))
311    }
312}
313
314impl Ord for Severity {
315    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
316        self.priority().cmp(&other.priority())
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn test_severity_ordering() {
326        assert!(Severity::Trace.priority() < Severity::Error.priority());
327        assert!(Severity::Warning.priority() < Severity::Critical.priority());
328    }
329
330    #[test]
331    fn test_severity_comparison() {
332        assert!(Severity::Trace < Severity::Error);
333        assert!(Severity::Info < Severity::Warning);
334        assert!(Severity::Warning < Severity::Critical);
335        assert!(Severity::Critical < Severity::Blocked);
336        assert!(Severity::Blocked < Severity::Error);
337    }
338
339    #[test]
340    fn test_severity_blocking() {
341        // Blocking
342        assert!(Severity::Error.is_blocking());
343        assert!(Severity::Blocked.is_blocking());
344
345        // Non-blocking (Critical is a warning!)
346        assert!(!Severity::Critical.is_blocking());
347        assert!(!Severity::Warning.is_blocking());
348        assert!(!Severity::Success.is_blocking());
349        assert!(!Severity::Completed.is_blocking());
350        assert!(!Severity::Info.is_blocking());
351        assert!(!Severity::Trace.is_blocking());
352    }
353
354    #[test]
355    fn test_severity_categorization() {
356        // Positive
357        assert!(Severity::Success.is_positive());
358        assert!(Severity::Completed.is_positive());
359
360        // Negative
361        assert!(Severity::Error.is_negative());
362        assert!(Severity::Warning.is_negative());
363        assert!(Severity::Critical.is_negative());
364        assert!(Severity::Blocked.is_negative());
365
366        // Neutral
367        assert!(Severity::Info.is_neutral());
368        assert!(Severity::Trace.is_neutral());
369
370        // Mutual exclusivity
371        assert!(!Severity::Error.is_positive());
372        assert!(!Severity::Success.is_negative());
373        assert!(!Severity::Info.is_positive());
374        assert!(!Severity::Info.is_negative());
375    }
376
377    #[test]
378    fn test_severity_metadata() {
379        assert_eq!(Severity::Error.as_str(), "Error");
380        assert_eq!(Severity::Warning.as_str(), "Warning");
381        assert_eq!(Severity::Critical.as_str(), "Critical");
382
383        assert_eq!(Severity::Error.description(), "Operation failed");
384        assert_eq!(Severity::Warning.description(), "Potential issue or caveat");
385
386        assert_eq!(Severity::Error.as_char(), 'E');
387        assert_eq!(Severity::Warning.as_char(), 'W');
388    }
389
390    #[test]
391    fn test_severity_positive() {
392        assert!(Severity::Success.is_positive());
393        assert!(Severity::Completed.is_positive());
394        assert!(!Severity::Error.is_positive());
395        assert!(!Severity::Warning.is_positive());
396        assert!(!Severity::Critical.is_positive());
397        assert!(!Severity::Blocked.is_positive());
398        assert!(!Severity::Info.is_positive());
399        assert!(!Severity::Trace.is_positive());
400    }
401
402    #[cfg(feature = "emoji")]
403    #[test]
404    fn test_emoji() {
405        assert_eq!(Severity::Error.emoji(), "❌");
406        assert_eq!(Severity::Warning.emoji(), "âš ī¸");
407        assert_eq!(Severity::Critical.emoji(), "đŸ”Ĩ");
408        assert_eq!(Severity::Success.emoji(), "✅");
409        assert_eq!(Severity::Completed.emoji(), "âœ”ī¸");
410        assert_eq!(Severity::Info.emoji(), "â„šī¸");
411        assert_eq!(Severity::Trace.emoji(), "🔍");
412        assert_eq!(Severity::Blocked.emoji(), "đŸšĢ");
413    }
414
415    #[cfg(feature = "ansi-colors")]
416    #[test]
417    fn test_ansi_colors() {
418        assert_eq!(Severity::Error.ansi_color(), "\x1b[1;31m");
419        assert_eq!(Severity::Warning.ansi_color(), "\x1b[33m");
420        assert_eq!(Severity::Success.ansi_color(), "\x1b[1;32m");
421        assert_eq!(Severity::ANSI_RESET, "\x1b[0m");
422    }
423
424    #[cfg(feature = "serde")]
425    #[test]
426    fn test_serde() {
427        use serde_json;
428
429        let severity = Severity::Error;
430        let json = serde_json::to_string(&severity).unwrap();
431        let deserialized: Severity = serde_json::from_str(&json).unwrap();
432        assert_eq!(severity, deserialized);
433    }
434}