Skip to main content

freeswitch_log_parser/
level.rs

1use std::fmt;
2use std::str::FromStr;
3
4/// FreeSWITCH log severity level.
5///
6/// Variants are ordered from least to most severe, so `level >= LogLevel::Warning`
7/// works naturally for filtering. `FromStr` is case-insensitive.
8///
9/// Note: FreeSWITCH uses `Err` (not `Error`) as the severity name.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum LogLevel {
12    Debug,
13    Info,
14    Notice,
15    Warning,
16    /// Equivalent to syslog `ERR` — not `Error`.
17    Err,
18    Crit,
19    Alert,
20    /// FreeSWITCH console output, highest severity in the ordering.
21    Console,
22}
23
24impl LogLevel {
25    /// All level names in severity order, matching `Display` output.
26    pub const ALL_LABELS: &[&str] = &[
27        "debug", "info", "notice", "warning", "err", "crit", "alert", "console",
28    ];
29}
30
31impl fmt::Display for LogLevel {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        let s = match self {
34            LogLevel::Debug => "debug",
35            LogLevel::Info => "info",
36            LogLevel::Notice => "notice",
37            LogLevel::Warning => "warning",
38            LogLevel::Err => "err",
39            LogLevel::Crit => "crit",
40            LogLevel::Alert => "alert",
41            LogLevel::Console => "console",
42        };
43        f.write_str(s)
44    }
45}
46
47/// Returned when a string doesn't match any known log level.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct ParseLevelError;
50
51impl fmt::Display for ParseLevelError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        f.write_str("invalid log level")
54    }
55}
56
57impl std::error::Error for ParseLevelError {}
58
59impl FromStr for LogLevel {
60    type Err = ParseLevelError;
61
62    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
63        if s.eq_ignore_ascii_case("debug") {
64            Ok(LogLevel::Debug)
65        } else if s.eq_ignore_ascii_case("info") {
66            Ok(LogLevel::Info)
67        } else if s.eq_ignore_ascii_case("notice") {
68            Ok(LogLevel::Notice)
69        } else if s.eq_ignore_ascii_case("warning") {
70            Ok(LogLevel::Warning)
71        } else if s.eq_ignore_ascii_case("err") {
72            Ok(LogLevel::Err)
73        } else if s.eq_ignore_ascii_case("crit") {
74            Ok(LogLevel::Crit)
75        } else if s.eq_ignore_ascii_case("alert") {
76            Ok(LogLevel::Alert)
77        } else if s.eq_ignore_ascii_case("console") {
78            Ok(LogLevel::Console)
79        } else {
80            Result::Err(ParseLevelError)
81        }
82    }
83}
84
85impl LogLevel {
86    /// Parses a bracketed level string like `[DEBUG]` or `[WARNING]`.
87    ///
88    /// Returns `None` if the input lacks brackets or contains an unrecognized level.
89    pub fn from_bracketed(s: &str) -> Option<LogLevel> {
90        let bytes = s.as_bytes();
91        if bytes.len() < 3 || bytes[0] != b'[' || bytes[bytes.len() - 1] != b']' {
92            return None;
93        }
94        s[1..s.len() - 1].parse().ok()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn from_str_round_trip() {
104        let variants = [
105            LogLevel::Debug,
106            LogLevel::Info,
107            LogLevel::Notice,
108            LogLevel::Warning,
109            LogLevel::Err,
110            LogLevel::Crit,
111            LogLevel::Alert,
112            LogLevel::Console,
113        ];
114        for v in variants {
115            let s = v.to_string();
116            let parsed: LogLevel = s.parse().unwrap();
117            assert_eq!(parsed, v, "round-trip failed for {v}");
118        }
119    }
120
121    #[test]
122    fn from_str_case_insensitive() {
123        assert_eq!("DEBUG".parse::<LogLevel>().unwrap(), LogLevel::Debug);
124        assert_eq!("Info".parse::<LogLevel>().unwrap(), LogLevel::Info);
125        assert_eq!("WARNING".parse::<LogLevel>().unwrap(), LogLevel::Warning);
126        assert_eq!("err".parse::<LogLevel>().unwrap(), LogLevel::Err);
127    }
128
129    #[test]
130    fn from_str_invalid() {
131        assert!("FAKE".parse::<LogLevel>().is_err());
132        assert!("".parse::<LogLevel>().is_err());
133        assert!("ERROR".parse::<LogLevel>().is_err());
134    }
135
136    #[test]
137    fn from_bracketed_all_variants() {
138        assert_eq!(LogLevel::from_bracketed("[DEBUG]"), Some(LogLevel::Debug));
139        assert_eq!(LogLevel::from_bracketed("[INFO]"), Some(LogLevel::Info));
140        assert_eq!(LogLevel::from_bracketed("[NOTICE]"), Some(LogLevel::Notice));
141        assert_eq!(
142            LogLevel::from_bracketed("[WARNING]"),
143            Some(LogLevel::Warning)
144        );
145        assert_eq!(LogLevel::from_bracketed("[ERR]"), Some(LogLevel::Err));
146        assert_eq!(LogLevel::from_bracketed("[CRIT]"), Some(LogLevel::Crit));
147        assert_eq!(LogLevel::from_bracketed("[ALERT]"), Some(LogLevel::Alert));
148        assert_eq!(
149            LogLevel::from_bracketed("[CONSOLE]"),
150            Some(LogLevel::Console)
151        );
152    }
153
154    #[test]
155    fn from_bracketed_rejects_malformed() {
156        assert_eq!(LogLevel::from_bracketed("[FAKE]"), None);
157        assert_eq!(LogLevel::from_bracketed("DEBUG"), None);
158        assert_eq!(LogLevel::from_bracketed("[]"), None);
159        assert_eq!(LogLevel::from_bracketed("["), None);
160        assert_eq!(LogLevel::from_bracketed(""), None);
161    }
162
163    #[test]
164    fn ord_severity_order() {
165        assert!(LogLevel::Debug < LogLevel::Info);
166        assert!(LogLevel::Info < LogLevel::Notice);
167        assert!(LogLevel::Notice < LogLevel::Warning);
168        assert!(LogLevel::Warning < LogLevel::Err);
169        assert!(LogLevel::Err < LogLevel::Crit);
170        assert!(LogLevel::Crit < LogLevel::Alert);
171        assert!(LogLevel::Alert < LogLevel::Console);
172    }
173}