use core::cmp::Ordering;
use core::fmt;
use core::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Level {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
Off = 255,
}
impl Level {
pub const fn as_str(self) -> &'static str {
match self {
Self::Off => "off",
Self::Trace => "trace",
Self::Debug => "debug",
Self::Info => "info",
Self::Warn => "warn",
Self::Error => "error",
}
}
pub const fn as_str_upper(self) -> &'static str {
match self {
Self::Off => "OFF",
Self::Trace => "TRACE",
Self::Debug => "DEBUG",
Self::Info => "INFO",
Self::Warn => "WARN",
Self::Error => "ERROR",
}
}
pub const fn is_enabled_at(self, threshold: Self) -> bool {
(self as u8) >= (threshold as u8)
}
pub const ALL: [Self; 5] = [
Self::Trace,
Self::Debug,
Self::Info,
Self::Warn,
Self::Error,
];
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl PartialOrd for Level {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Level {
fn cmp(&self, other: &Self) -> Ordering {
(*self as u8).cmp(&(*other as u8))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseLevelError;
impl fmt::Display for ParseLevelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid log level (expected one of: off, trace, debug, info, warn, error)")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseLevelError {}
impl FromStr for Level {
type Err = ParseLevelError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("off") {
Ok(Self::Off)
} else if s.eq_ignore_ascii_case("trace") {
Ok(Self::Trace)
} else if s.eq_ignore_ascii_case("debug") {
Ok(Self::Debug)
} else if s.eq_ignore_ascii_case("info") {
Ok(Self::Info)
} else if s.eq_ignore_ascii_case("warn") || s.eq_ignore_ascii_case("warning") {
Ok(Self::Warn)
} else if s.eq_ignore_ascii_case("error") || s.eq_ignore_ascii_case("err") {
Ok(Self::Error)
} else {
Err(ParseLevelError)
}
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn ordering_is_total_and_severity_aligned() {
assert!(Level::Trace < Level::Debug);
assert!(Level::Debug < Level::Info);
assert!(Level::Info < Level::Warn);
assert!(Level::Warn < Level::Error);
assert!(Level::Error < Level::Off);
}
#[test]
fn is_enabled_at_respects_threshold() {
assert!(Level::Error.is_enabled_at(Level::Info));
assert!(Level::Info.is_enabled_at(Level::Info));
assert!(!Level::Debug.is_enabled_at(Level::Info));
assert!(!Level::Trace.is_enabled_at(Level::Warn));
}
#[test]
fn parse_round_trip_canonical_forms() {
for level in Level::ALL {
let parsed: Level = level.as_str().parse().unwrap();
assert_eq!(parsed, level);
let parsed_upper: Level = level.as_str_upper().parse().unwrap();
assert_eq!(parsed_upper, level);
}
assert_eq!("off".parse::<Level>().unwrap(), Level::Off);
assert_eq!("OFF".parse::<Level>().unwrap(), Level::Off);
}
#[test]
fn parse_aliases() {
assert_eq!("warning".parse::<Level>().unwrap(), Level::Warn);
assert_eq!("err".parse::<Level>().unwrap(), Level::Error);
}
#[test]
fn parse_rejects_garbage() {
assert!("verbose".parse::<Level>().is_err());
assert!("".parse::<Level>().is_err());
}
#[test]
fn display_lowercase() {
assert_eq!(Level::Info.to_string(), "info");
}
#[test]
fn upper_form() {
assert_eq!(Level::Warn.as_str_upper(), "WARN");
}
}