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 | 7 | Operation failed |
14/// | Blocked | B | 6 | Execution blocked/waiting |
15/// | Critical | C | 5 | Severe issue requiring attention |
16/// | Warning | W | 4 | Potential issue |
17/// | Success | S | 3 | Operation succeeded |
18/// | Completed | K | 2 | Task/phase completed |
19/// | Info | I | 1 | Informational events |
20/// | Trace | T | 0 | Execution traces |
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[repr(u8)]
24pub enum Severity {
25    /// **Error (E)** - Operation failed
26    Error = b'E',
27    /// **Warning (W)** - Potential issue or caveat
28    Warning = b'W',
29    /// **Critical (C)** - Severe issue requiring attention
30    Critical = b'C',
31    /// **Blocked (B)** - Execution blocked/waiting
32    Blocked = b'B',
33    /// **Success (S)** - Operation succeeded
34    Success = b'S',
35    /// **Completed (K)** - Task or phase completed
36    Completed = b'K',
37    /// **Info (I)** - General informational events
38    Info = b'I',
39    /// **Trace (T)** - Execution traces and instrumentation
40    Trace = b'T',
41}
42
43impl Severity {
44    /// Get the single-character code (E, W, C, B, S, K, I, T)
45    pub const fn as_char(self) -> char {
46        self as u8 as char
47    }
48
49    /// Get the full name as a string slice (e.g., "Error", "Warning")
50    pub const fn as_str(self) -> &'static str {
51        match self {
52            Severity::Error => "Error",
53            Severity::Warning => "Warning",
54            Severity::Critical => "Critical",
55            Severity::Blocked => "Blocked",
56            Severity::Success => "Success",
57            Severity::Completed => "Completed",
58            Severity::Info => "Info",
59            Severity::Trace => "Trace",
60        }
61    }
62
63    /// Get a human-readable description of this severity level
64    pub const fn description(self) -> &'static str {
65        match self {
66            Severity::Error => "Operation failed",
67            Severity::Warning => "Potential issue or caveat",
68            Severity::Critical => "Severe issue requiring attention",
69            Severity::Blocked => "Execution blocked or waiting",
70            Severity::Success => "Operation succeeded",
71            Severity::Completed => "Task or phase completed",
72            Severity::Info => "General informational events",
73            Severity::Trace => "Execution traces and instrumentation",
74        }
75    }
76
77    /// Get the priority level (0-7, higher = more severe)
78    ///
79    /// **Priority Scale:**
80    /// - 0-1: Diagnostic (Trace, Info)
81    /// - 2-3: Positive (Completed, Success)
82    /// - 4-5: Issues (Warning, Critical)
83    /// - 6-7: Blocking (Blocked, Error)
84    pub const fn priority(self) -> u8 {
85        match self {
86            Severity::Trace => 0,
87            Severity::Info => 1,
88            Severity::Completed => 2,
89            Severity::Success => 3,
90            Severity::Warning => 4,
91            Severity::Critical => 5,
92            Severity::Blocked => 6,
93            Severity::Error => 7,
94        }
95    }
96
97    /// Check if this severity **blocks execution**
98    ///
99    /// Returns `true` only for Error and Blocked.
100    /// Critical is a severe warning but does NOT block.
101    pub const fn is_blocking(self) -> bool {
102        matches!(self, Severity::Error | Severity::Blocked)
103    }
104
105    /// Check if this is a **positive outcome** (Success/Completed)
106    pub const fn is_positive(self) -> bool {
107        matches!(self, Severity::Success | Severity::Completed)
108    }
109
110    /// Check if this is a **negative outcome** (Error/Warning/Critical/Blocked)
111    pub const fn is_negative(self) -> bool {
112        matches!(
113            self,
114            Severity::Error | Severity::Warning | Severity::Critical | Severity::Blocked
115        )
116    }
117
118    /// Check if this is **neutral** (Info/Trace)
119    pub const fn is_neutral(self) -> bool {
120        matches!(self, Severity::Info | Severity::Trace)
121    }
122
123    /// Get emoji representation for visual display (requires "emoji" feature)
124    ///
125    /// Returns a unicode emoji that visually represents the severity level.
126    /// Perfect for modern terminal UIs, logs, and documentation.
127    ///
128    /// # Emoji Mapping
129    ///
130    /// - Error: ❌ (cross mark)
131    /// - Blocked: đŸšĢ (prohibited)
132    /// - Critical: đŸ”Ĩ (fire - severe issue!)
133    /// - Warning: âš ī¸ (warning sign)
134    /// - Success: ✅ (check mark)
135    /// - Completed: âœ”ī¸ (check mark button)
136    /// - Info: â„šī¸ (information)
137    /// - Trace: 🔍 (magnifying glass)
138    ///
139    /// # Examples
140    ///
141    /// ```rust
142    /// use waddling_errors::Severity;
143    ///
144    /// println!("{} Error occurred!", Severity::Error.emoji());
145    /// println!("{} Build successful!", Severity::Success.emoji());
146    /// ```
147    #[cfg(feature = "emoji")]
148    pub const fn emoji(self) -> &'static str {
149        match self {
150            Severity::Error => "❌",
151            Severity::Blocked => "đŸšĢ",
152            Severity::Critical => "đŸ”Ĩ",
153            Severity::Warning => "âš ī¸",
154            Severity::Success => "✅",
155            Severity::Completed => "âœ”ī¸",
156            Severity::Info => "â„šī¸",
157            Severity::Trace => "🔍",
158        }
159    }
160
161    /// Get ANSI color code for terminal display (requires "ansi-colors" feature)
162    ///
163    /// Returns the ANSI escape sequence to colorize terminal output.
164    ///
165    /// # Color Scheme
166    ///
167    /// - Error: Red (bold)
168    /// - Blocked: Red
169    /// - Critical: Yellow (bold)
170    /// - Warning: Yellow
171    /// - Success: Green (bold)
172    /// - Completed: Green
173    /// - Info: Cyan
174    /// - Trace: Blue (dim)
175    #[cfg(feature = "ansi-colors")]
176    pub const fn ansi_color(self) -> &'static str {
177        match self {
178            Severity::Error => "\x1b[1;31m",    // Bold Red
179            Severity::Blocked => "\x1b[31m",    // Red
180            Severity::Critical => "\x1b[1;33m", // Bold Yellow
181            Severity::Warning => "\x1b[33m",    // Yellow
182            Severity::Success => "\x1b[1;32m",  // Bold Green
183            Severity::Completed => "\x1b[32m",  // Green
184            Severity::Info => "\x1b[36m",       // Cyan
185            Severity::Trace => "\x1b[2;34m",    // Dim Blue
186        }
187    }
188
189    /// ANSI reset code (requires "ansi-colors" feature)
190    #[cfg(feature = "ansi-colors")]
191    pub const ANSI_RESET: &'static str = "\x1b[0m";
192}
193
194impl fmt::Display for Severity {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        write!(f, "{}", self.as_char())
197    }
198}
199
200impl PartialOrd for Severity {
201    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
202        Some(self.cmp(other))
203    }
204}
205
206impl Ord for Severity {
207    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
208        self.priority().cmp(&other.priority())
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    #[test]
217    fn test_severity_ordering() {
218        assert!(Severity::Trace.priority() < Severity::Error.priority());
219        assert!(Severity::Warning.priority() < Severity::Critical.priority());
220    }
221
222    #[test]
223    fn test_severity_comparison() {
224        assert!(Severity::Trace < Severity::Error);
225        assert!(Severity::Info < Severity::Warning);
226        assert!(Severity::Warning < Severity::Critical);
227        assert!(Severity::Critical < Severity::Blocked);
228        assert!(Severity::Blocked < Severity::Error);
229    }
230
231    #[test]
232    fn test_severity_blocking() {
233        // Blocking
234        assert!(Severity::Error.is_blocking());
235        assert!(Severity::Blocked.is_blocking());
236
237        // Non-blocking (Critical is a warning!)
238        assert!(!Severity::Critical.is_blocking());
239        assert!(!Severity::Warning.is_blocking());
240        assert!(!Severity::Success.is_blocking());
241        assert!(!Severity::Completed.is_blocking());
242        assert!(!Severity::Info.is_blocking());
243        assert!(!Severity::Trace.is_blocking());
244    }
245
246    #[test]
247    fn test_severity_categorization() {
248        // Positive
249        assert!(Severity::Success.is_positive());
250        assert!(Severity::Completed.is_positive());
251
252        // Negative
253        assert!(Severity::Error.is_negative());
254        assert!(Severity::Warning.is_negative());
255        assert!(Severity::Critical.is_negative());
256        assert!(Severity::Blocked.is_negative());
257
258        // Neutral
259        assert!(Severity::Info.is_neutral());
260        assert!(Severity::Trace.is_neutral());
261
262        // Mutual exclusivity
263        assert!(!Severity::Error.is_positive());
264        assert!(!Severity::Success.is_negative());
265        assert!(!Severity::Info.is_positive());
266        assert!(!Severity::Info.is_negative());
267    }
268
269    #[test]
270    fn test_severity_metadata() {
271        assert_eq!(Severity::Error.as_str(), "Error");
272        assert_eq!(Severity::Warning.as_str(), "Warning");
273        assert_eq!(Severity::Critical.as_str(), "Critical");
274
275        assert_eq!(Severity::Error.description(), "Operation failed");
276        assert_eq!(Severity::Warning.description(), "Potential issue or caveat");
277
278        assert_eq!(Severity::Error.as_char(), 'E');
279        assert_eq!(Severity::Warning.as_char(), 'W');
280    }
281
282    #[test]
283    fn test_severity_positive() {
284        assert!(Severity::Success.is_positive());
285        assert!(Severity::Completed.is_positive());
286        assert!(!Severity::Error.is_positive());
287        assert!(!Severity::Warning.is_positive());
288        assert!(!Severity::Critical.is_positive());
289        assert!(!Severity::Blocked.is_positive());
290        assert!(!Severity::Info.is_positive());
291        assert!(!Severity::Trace.is_positive());
292    }
293
294    #[cfg(feature = "emoji")]
295    #[test]
296    fn test_emoji() {
297        assert_eq!(Severity::Error.emoji(), "❌");
298        assert_eq!(Severity::Warning.emoji(), "âš ī¸");
299        assert_eq!(Severity::Critical.emoji(), "đŸ”Ĩ");
300        assert_eq!(Severity::Success.emoji(), "✅");
301        assert_eq!(Severity::Completed.emoji(), "âœ”ī¸");
302        assert_eq!(Severity::Info.emoji(), "â„šī¸");
303        assert_eq!(Severity::Trace.emoji(), "🔍");
304        assert_eq!(Severity::Blocked.emoji(), "đŸšĢ");
305    }
306
307    #[cfg(feature = "ansi-colors")]
308    #[test]
309    fn test_ansi_colors() {
310        assert_eq!(Severity::Error.ansi_color(), "\x1b[1;31m");
311        assert_eq!(Severity::Warning.ansi_color(), "\x1b[33m");
312        assert_eq!(Severity::Success.ansi_color(), "\x1b[1;32m");
313        assert_eq!(Severity::ANSI_RESET, "\x1b[0m");
314    }
315
316    #[cfg(feature = "serde")]
317    #[test]
318    fn test_serde() {
319        use serde_json;
320
321        let severity = Severity::Error;
322        let json = serde_json::to_string(&severity).unwrap();
323        let deserialized: Severity = serde_json::from_str(&json).unwrap();
324        assert_eq!(severity, deserialized);
325    }
326}