use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
#[repr(u8)]
pub enum LogLevel {
Trace = 0,
Debug = 1,
#[default]
Info = 2,
Warn = 3,
Error = 4,
Fatal = 5,
}
impl LogLevel {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"TRACE" => Some(LogLevel::Trace),
"DEBUG" => Some(LogLevel::Debug),
"INFO" => Some(LogLevel::Info),
"WARN" | "WARNING" => Some(LogLevel::Warn),
"ERROR" => Some(LogLevel::Error),
"FATAL" | "CRITICAL" => Some(LogLevel::Fatal),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
LogLevel::Trace => "TRACE",
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
LogLevel::Error => "ERROR",
LogLevel::Fatal => "FATAL",
}
}
pub fn as_short_str(&self) -> &'static str {
match self {
LogLevel::Trace => "TRC",
LogLevel::Debug => "DBG",
LogLevel::Info => "INF",
LogLevel::Warn => "WRN",
LogLevel::Error => "ERR",
LogLevel::Fatal => "FTL",
}
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for LogLevel {
type Err = LogLevelParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str(s).ok_or_else(|| LogLevelParseError::Unknown(s.to_string()))
}
}
#[derive(Error, Debug)]
pub enum LogLevelParseError {
#[error("Unknown log level: {0}")]
Unknown(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_str_valid() {
assert_eq!(LogLevel::from_str("INFO"), Some(LogLevel::Info));
assert_eq!(LogLevel::from_str("info"), Some(LogLevel::Info));
assert_eq!(LogLevel::from_str("INFO"), Some(LogLevel::Info));
assert_eq!(LogLevel::from_str("WARN"), Some(LogLevel::Warn));
assert_eq!(LogLevel::from_str("WARNING"), Some(LogLevel::Warn));
assert_eq!(LogLevel::from_str("ERROR"), Some(LogLevel::Error));
assert_eq!(LogLevel::from_str("FATAL"), Some(LogLevel::Fatal));
assert_eq!(LogLevel::from_str("CRITICAL"), Some(LogLevel::Fatal));
}
#[test]
fn test_from_str_invalid() {
assert_eq!(LogLevel::from_str("INVALID"), None);
assert_eq!(LogLevel::from_str(""), None);
}
#[test]
fn test_as_str() {
assert_eq!(LogLevel::Info.as_str(), "INFO");
assert_eq!(LogLevel::Error.as_str(), "ERROR");
}
#[test]
fn test_ordering() {
assert!(LogLevel::Error > LogLevel::Debug);
assert!(LogLevel::Info <= LogLevel::Warn);
assert!(LogLevel::Trace < LogLevel::Fatal);
}
#[test]
fn test_as_str_all_variants() {
assert_eq!(LogLevel::Trace.as_str(), "TRACE");
assert_eq!(LogLevel::Debug.as_str(), "DEBUG");
assert_eq!(LogLevel::Info.as_str(), "INFO");
assert_eq!(LogLevel::Warn.as_str(), "WARN");
assert_eq!(LogLevel::Error.as_str(), "ERROR");
assert_eq!(LogLevel::Fatal.as_str(), "FATAL");
}
#[test]
fn test_as_short_str_all_variants() {
assert_eq!(LogLevel::Trace.as_short_str(), "TRC");
assert_eq!(LogLevel::Debug.as_short_str(), "DBG");
assert_eq!(LogLevel::Info.as_short_str(), "INF");
assert_eq!(LogLevel::Warn.as_short_str(), "WRN");
assert_eq!(LogLevel::Error.as_short_str(), "ERR");
assert_eq!(LogLevel::Fatal.as_short_str(), "FTL");
}
#[test]
fn test_display_matches_as_str() {
for level in [
LogLevel::Trace,
LogLevel::Debug,
LogLevel::Info,
LogLevel::Warn,
LogLevel::Error,
LogLevel::Fatal,
] {
assert_eq!(format!("{}", level), level.as_str());
}
}
#[test]
fn test_from_str_trait_valid() {
let level: LogLevel = "debug".parse().expect("valid level should parse");
assert_eq!(level, LogLevel::Debug);
let level: LogLevel = "WARNING".parse().expect("WARNING should parse to Warn");
assert_eq!(level, LogLevel::Warn);
let level: LogLevel = "CRITICAL".parse().expect("CRITICAL should parse to Fatal");
assert_eq!(level, LogLevel::Fatal);
}
#[test]
fn test_from_str_trait_invalid_returns_error() {
let result: Result<LogLevel, _> = "invalid_level".parse();
let err = result.expect_err("invalid level should error");
assert!(format!("{}", err).contains("Unknown log level"));
assert!(format!("{}", err).contains("invalid_level"));
}
#[test]
fn test_from_str_trait_empty_string() {
let result: Result<LogLevel, _> = "".parse();
assert!(result.is_err());
}
}