Skip to main content

openvpn_mgmt_codec/
log_level.rs

1use std::str::FromStr;
2
3/// Error returned when a string is not a recognized log level.
4#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
5#[error("unrecognized log level: {0:?}")]
6pub struct ParseLogLevelError(pub String);
7
8/// Log severity level from `>LOG:` notifications.
9#[derive(Debug, Clone, PartialEq, Eq, strum::Display)]
10pub enum LogLevel {
11    /// Informational message (`I`).
12    #[strum(to_string = "I")]
13    Info,
14
15    /// Debug message (`D`).
16    #[strum(to_string = "D")]
17    Debug,
18
19    /// Warning (`W`).
20    #[strum(to_string = "W")]
21    Warning,
22
23    /// Non-fatal error (`N`).
24    #[strum(to_string = "N")]
25    NonFatal,
26
27    /// Fatal error (`F`).
28    #[strum(to_string = "F")]
29    Fatal,
30
31    /// An unrecognized log flag (forward compatibility).
32    #[strum(default)]
33    Unknown(String),
34}
35
36impl LogLevel {
37    /// Human-readable label (e.g. `"INFO"`, `"WARN"`).
38    pub fn label(&self) -> &str {
39        match self {
40            Self::Info => "INFO",
41            Self::Debug => "DEBUG",
42            Self::Warning => "WARN",
43            Self::NonFatal => "NFATAL",
44            Self::Fatal => "FATAL",
45            Self::Unknown(s) => s,
46        }
47    }
48}
49
50impl FromStr for LogLevel {
51    type Err = ParseLogLevelError;
52
53    /// Parse a recognized log-flag string: `I`, `D`, `W`, `N`, `F`.
54    fn from_str(input: &str) -> Result<Self, Self::Err> {
55        match input {
56            "I" => Ok(Self::Info),
57            "D" => Ok(Self::Debug),
58            "W" => Ok(Self::Warning),
59            "N" => Ok(Self::NonFatal),
60            "F" => Ok(Self::Fatal),
61            other => Err(ParseLogLevelError(other.to_string())),
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use test_case::test_case;
70
71    #[test_case("I" => LogLevel::Info)]
72    #[test_case("D" => LogLevel::Debug)]
73    #[test_case("W" => LogLevel::Warning)]
74    #[test_case("N" => LogLevel::NonFatal)]
75    #[test_case("F" => LogLevel::Fatal)]
76    fn parse_roundtrip(flag: &str) -> LogLevel {
77        flag.parse().unwrap()
78    }
79
80    #[test]
81    fn parse_invalid() {
82        let err = "X".parse::<LogLevel>().unwrap_err();
83        assert_eq!(err.to_string(), r#"unrecognized log level: "X""#);
84    }
85
86    #[test]
87    fn label_known_variants() {
88        assert_eq!(LogLevel::Info.label(), "INFO");
89        assert_eq!(LogLevel::Debug.label(), "DEBUG");
90        assert_eq!(LogLevel::Warning.label(), "WARN");
91        assert_eq!(LogLevel::NonFatal.label(), "NFATAL");
92        assert_eq!(LogLevel::Fatal.label(), "FATAL");
93    }
94
95    #[test]
96    fn label_unknown_returns_inner() {
97        let level = LogLevel::Unknown("Z".to_string());
98        assert_eq!(level.label(), "Z");
99    }
100
101    #[test]
102    fn display_unknown_uses_strum_default() {
103        let level = LogLevel::Unknown("Q".to_string());
104        assert_eq!(level.to_string(), "Q");
105    }
106}