Skip to main content

cargo_msrv/
log_level.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use clap::ValueEnum;
5
6#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, ValueEnum)]
7pub enum LogLevel {
8    Trace,
9    Debug,
10    #[default]
11    Info,
12    Warn,
13    Error,
14}
15
16impl LogLevel {
17    fn variants() -> &'static [&'static str] {
18        &["trace", "debug", "info", "warn", "error"]
19    }
20}
21
22impl Display for LogLevel {
23    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
24        f.write_fmt(format_args!("{:?}", self))
25    }
26}
27
28impl FromStr for LogLevel {
29    type Err = ParseLogLevelError;
30
31    fn from_str(input: &str) -> Result<Self, Self::Err> {
32        fn parse_num_level(input: &str) -> Option<LogLevel> {
33            input.parse::<u8>().ok().and_then(|number| match number {
34                1 => Some(LogLevel::Error),
35                2 => Some(LogLevel::Warn),
36                3 => Some(LogLevel::Info),
37                4 => Some(LogLevel::Debug),
38                5 => Some(LogLevel::Trace),
39                _ => None,
40            })
41        }
42
43        fn parse_str_level(input: &str) -> Option<LogLevel> {
44            match input {
45                s if s.eq_ignore_ascii_case("error") => Some(LogLevel::Error),
46                s if s.eq_ignore_ascii_case("warn") => Some(LogLevel::Warn),
47                s if s.eq_ignore_ascii_case("info") => Some(LogLevel::Info),
48                s if s.eq_ignore_ascii_case("debug") => Some(LogLevel::Debug),
49                s if s.eq_ignore_ascii_case("trace") => Some(LogLevel::Trace),
50                _ => None,
51            }
52        }
53
54        parse_num_level(input)
55            .or_else(|| parse_str_level(input))
56            .ok_or_else(|| ParseLogLevelError::NoMatchingLevel {
57                given_input: input.to_string(),
58                valid_options_formatted: Self::variants().join(","),
59            })
60    }
61}
62
63impl From<LogLevel> for tracing::Level {
64    fn from(level: LogLevel) -> Self {
65        match level {
66            LogLevel::Trace => tracing::Level::TRACE,
67            LogLevel::Debug => tracing::Level::DEBUG,
68            LogLevel::Info => tracing::Level::INFO,
69            LogLevel::Warn => tracing::Level::WARN,
70            LogLevel::Error => tracing::Level::ERROR,
71        }
72    }
73}
74
75#[derive(Debug, thiserror::Error)]
76pub enum ParseLogLevelError {
77    #[error(
78        "The given log level '{given_input}' does not exist, valid options are: {valid_options_formatted}]"
79    )]
80    NoMatchingLevel {
81        given_input: String,
82        valid_options_formatted: String,
83    },
84}
85
86#[cfg(test)]
87mod tests {
88    use crate::log_level::{LogLevel, ParseLogLevelError};
89
90    #[yare::parameterized(
91        trace_numeric = {  "5", LogLevel::Trace },
92        trace_alphabetic = { "trace", LogLevel::Trace },
93        debug_numeric = {  "4", LogLevel::Debug },
94        debug_alphabetic = { "debug", LogLevel::Debug },
95        debug_alphabetic_uppercase = { "DEBUG", LogLevel::Debug },
96        info_numeric = {  "3", LogLevel::Info },
97        info_alphabetic = { "info", LogLevel::Info },
98        info_alphabetic_uppercase = { "INFO", LogLevel::Info },
99        warn_numeric = {  "2", LogLevel::Warn },
100        warn_alphabetic = { "warn", LogLevel::Warn },
101        warn_alphabetic_uppercase = { "WARN", LogLevel::Warn },
102        error_numeric = {  "1", LogLevel::Error },
103        error_alphabetic = { "error", LogLevel::Error },
104        error_alphabetic_uppercase = { "ERROR", LogLevel::Error },
105    )]
106    fn valid_log_levels(input: &str, expected: LogLevel) {
107        assert_eq!(input.parse::<LogLevel>().unwrap(), expected);
108    }
109
110    #[yare::parameterized(
111        numeric_floor = { "0" },
112        numeric_ceil = { "6" },
113        non_existing = { "null" },
114        empty = { "" },
115    )]
116    fn invalid_log_levels(input: &str) {
117        let expected_err = input.parse::<LogLevel>().unwrap_err();
118
119        match expected_err {
120            ParseLogLevelError::NoMatchingLevel {
121                ref given_input, ..
122            } => assert_eq!(given_input, input),
123        };
124    }
125}