use std::{fmt, str::FromStr};
use cfg_if::cfg_if;
use crate::Error;
pub(crate) const LOG_LEVEL_NAMES: [&str; Level::count()] =
["critical", "error", "warn", "info", "debug", "trace"];
const LOG_LEVEL_SHORT_NAMES: [&str; Level::count()] = ["C", "E", "W", "I", "D", "T"];
#[repr(u16)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Level {
Critical = 0,
Error,
Warn,
Info,
Debug,
Trace,
}
cfg_if! {
if #[cfg(test)] {
static_assertions::const_assert!(atomic::Atomic::<Level>::is_lock_free());
}
}
impl Level {
#[must_use]
fn from_usize(u: usize) -> Option<Level> {
match u {
0 => Some(Level::Critical),
1 => Some(Level::Error),
2 => Some(Level::Warn),
3 => Some(Level::Info),
4 => Some(Level::Debug),
5 => Some(Level::Trace),
_ => None,
}
}
#[must_use]
const fn min_usize() -> usize {
Self::most_severe() as usize
}
#[must_use]
const fn max_usize() -> usize {
Self::most_verbose() as usize
}
#[must_use]
pub(crate) const fn count() -> usize {
Self::max_usize() + 1
}
#[must_use]
pub const fn most_severe() -> Level {
Level::Critical
}
#[must_use]
pub const fn most_verbose() -> Level {
Level::Trace
}
#[must_use]
pub fn as_str(&self) -> &'static str {
LOG_LEVEL_NAMES[*self as usize]
}
#[must_use]
pub(crate) fn as_short_str(&self) -> &'static str {
LOG_LEVEL_SHORT_NAMES[*self as usize]
}
pub fn iter() -> impl Iterator<Item = Self> {
(Self::min_usize()..=Self::max_usize()).map(|i| Self::from_usize(i).unwrap())
}
}
#[cfg(feature = "log")]
impl From<log::Level> for Level {
fn from(level: log::Level) -> Self {
match level {
log::Level::Error => Self::Error,
log::Level::Warn => Self::Warn,
log::Level::Info => Self::Info,
log::Level::Debug => Self::Debug,
log::Level::Trace => Self::Trace,
}
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Level {
type Err = Error;
fn from_str(level: &str) -> Result<Level, Self::Err> {
LOG_LEVEL_NAMES
.iter()
.position(|&name| name.eq_ignore_ascii_case(level))
.into_iter()
.map(|idx| Level::from_usize(idx).unwrap())
.next()
.ok_or_else(|| Error::ParseLevel(level.to_string()))
}
}
#[repr(align(4))]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum LevelFilter {
Off,
Equal(Level),
NotEqual(Level),
MoreSevere(Level),
MoreSevereEqual(Level),
MoreVerbose(Level),
MoreVerboseEqual(Level),
All,
}
cfg_if! {
if #[cfg(test)] {
static_assertions::const_assert!(atomic::Atomic::<LevelFilter>::is_lock_free());
static_assertions::const_assert_eq!(
std::mem::size_of::<Level>() * 2,
std::mem::size_of::<LevelFilter>()
);
static_assertions::const_assert_eq!(
std::mem::align_of::<Level>() * 2,
std::mem::align_of::<LevelFilter>()
);
}
}
impl LevelFilter {
#[must_use]
pub fn compare(&self, level: Level) -> bool {
self.__compare_const(level)
}
#[doc(hidden)]
#[must_use]
pub const fn __compare_const(&self, level: Level) -> bool {
let level_num: u16 = level as u16;
match *self {
Self::Off => false,
Self::Equal(stored) => level_num == stored as u16,
Self::NotEqual(stored) => level_num != stored as u16,
Self::MoreSevere(stored) => level_num < stored as u16,
Self::MoreSevereEqual(stored) => level_num <= stored as u16,
Self::MoreVerbose(stored) => level_num > stored as u16,
Self::MoreVerboseEqual(stored) => level_num >= stored as u16,
Self::All => true,
}
}
#[must_use]
pub(crate) fn from_str_for_env(text: &str) -> Option<LevelFilter> {
if let Ok(level) = Level::from_str(text) {
Some(LevelFilter::MoreSevereEqual(level))
} else if text.eq_ignore_ascii_case("off") {
Some(LevelFilter::Off)
} else if text.eq_ignore_ascii_case("all") {
Some(LevelFilter::All)
} else {
None
}
}
}
#[cfg(feature = "log")]
impl From<log::LevelFilter> for LevelFilter {
fn from(filter: log::LevelFilter) -> Self {
match filter {
log::LevelFilter::Off => Self::Off,
filter => Self::MoreSevereEqual(Level::from(filter.to_level().unwrap())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_usize() {
assert_eq!(
Level::most_severe(),
Level::from_usize(Level::min_usize()).unwrap()
);
assert_eq!(
Level::most_verbose(),
Level::from_usize(Level::max_usize()).unwrap()
);
}
#[test]
fn from_str() {
fn to_random_case(input: &str) -> String {
input
.char_indices()
.map(|(i, char)| {
if i % 2 != 0 {
char.to_ascii_uppercase()
} else {
char.to_ascii_lowercase()
}
})
.collect::<String>()
}
for (i, &name) in LOG_LEVEL_NAMES.iter().enumerate() {
let from_usize = Level::from_usize(i);
let from_str = (
Level::from_str(&name.to_lowercase()),
Level::from_str(&name.to_uppercase()),
Level::from_str(&to_random_case(name)),
);
assert_eq!(from_usize.unwrap(), from_str.0.unwrap());
assert_eq!(from_usize.unwrap(), from_str.1.unwrap());
assert_eq!(from_usize.unwrap(), from_str.2.unwrap());
}
assert!(Level::from_str("notexist").is_err());
}
#[test]
fn as_short_str() {
for (&name, &short_name) in LOG_LEVEL_NAMES.iter().zip(LOG_LEVEL_SHORT_NAMES.iter()) {
assert_eq!(
name.chars()
.next()
.unwrap()
.to_ascii_uppercase()
.to_string(),
short_name
);
}
}
#[test]
fn level_filter_from_str_for_env() {
assert_eq!(
LevelFilter::MoreSevereEqual(Level::Info),
LevelFilter::from_str_for_env("iNFo").unwrap()
);
assert_eq!(
LevelFilter::MoreSevereEqual(Level::Warn),
LevelFilter::from_str_for_env("wARn").unwrap()
);
assert_eq!(
LevelFilter::Off,
LevelFilter::from_str_for_env("oFf").unwrap()
);
assert_eq!(
LevelFilter::All,
LevelFilter::from_str_for_env("aLl").unwrap()
);
}
#[test]
fn iter() {
let mut iter = Level::iter();
assert_eq!(iter.next(), Some(Level::Critical));
assert_eq!(iter.next(), Some(Level::Error));
assert_eq!(iter.next(), Some(Level::Warn));
assert_eq!(iter.next(), Some(Level::Info));
assert_eq!(iter.next(), Some(Level::Debug));
assert_eq!(iter.next(), Some(Level::Trace));
assert_eq!(iter.next(), None);
}
#[test]
fn filter() {
assert!(!LevelFilter::Off.compare(Level::Trace));
assert!(!LevelFilter::Off.compare(Level::Critical));
assert!(!LevelFilter::Off.compare(Level::Warn));
assert!(LevelFilter::Equal(Level::Error).compare(Level::Error));
assert!(!LevelFilter::Equal(Level::Error).compare(Level::Warn));
assert!(!LevelFilter::Equal(Level::Error).compare(Level::Critical));
assert!(LevelFilter::NotEqual(Level::Error).compare(Level::Trace));
assert!(LevelFilter::NotEqual(Level::Error).compare(Level::Info));
assert!(!LevelFilter::NotEqual(Level::Error).compare(Level::Error));
assert!(LevelFilter::MoreSevere(Level::Info).compare(Level::Warn));
assert!(LevelFilter::MoreSevere(Level::Info).compare(Level::Error));
assert!(!LevelFilter::MoreSevere(Level::Info).compare(Level::Info));
assert!(LevelFilter::MoreSevereEqual(Level::Info).compare(Level::Warn));
assert!(LevelFilter::MoreSevereEqual(Level::Info).compare(Level::Info));
assert!(!LevelFilter::MoreSevereEqual(Level::Info).compare(Level::Trace));
assert!(LevelFilter::MoreVerbose(Level::Error).compare(Level::Warn));
assert!(LevelFilter::MoreVerbose(Level::Error).compare(Level::Info));
assert!(!LevelFilter::MoreVerbose(Level::Error).compare(Level::Error));
assert!(LevelFilter::MoreVerboseEqual(Level::Error).compare(Level::Warn));
assert!(LevelFilter::MoreVerboseEqual(Level::Error).compare(Level::Error));
assert!(!LevelFilter::MoreVerboseEqual(Level::Error).compare(Level::Critical));
assert!(LevelFilter::All.compare(Level::Trace));
assert!(LevelFilter::All.compare(Level::Critical));
assert!(LevelFilter::All.compare(Level::Error));
}
#[cfg(feature = "log")]
#[test]
fn filter_from_log() {
assert_eq!(LevelFilter::from(log::LevelFilter::Off), LevelFilter::Off);
assert_eq!(
LevelFilter::from(log::LevelFilter::Error),
LevelFilter::MoreSevereEqual(Level::Error)
);
assert_eq!(
LevelFilter::from(log::LevelFilter::Warn),
LevelFilter::MoreSevereEqual(Level::Warn)
);
assert_eq!(
LevelFilter::from(log::LevelFilter::Info),
LevelFilter::MoreSevereEqual(Level::Info)
);
assert_eq!(
LevelFilter::from(log::LevelFilter::Debug),
LevelFilter::MoreSevereEqual(Level::Debug)
);
assert_eq!(
LevelFilter::from(log::LevelFilter::Trace),
LevelFilter::MoreSevereEqual(Level::Trace)
);
}
}