use core::fmt;
use core::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Level {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
impl Level {
pub const ALL: [Self; 8] = [
Self::Debug,
Self::Info,
Self::Notice,
Self::Warning,
Self::Error,
Self::Critical,
Self::Alert,
Self::Emergency,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Debug => "debug",
Self::Info => "info",
Self::Notice => "notice",
Self::Warning => "warning",
Self::Error => "error",
Self::Critical => "critical",
Self::Alert => "alert",
Self::Emergency => "emergency",
}
}
pub fn parse(text: &str) -> Result<Self, LevelError> {
Self::ALL
.into_iter()
.find(|level| level.as_str() == text)
.ok_or_else(|| LevelError(text.to_owned()))
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("`{0}` is not a logging level")]
pub struct LevelError(pub String);
impl FromStr for Level {
type Err = LevelError;
fn from_str(text: &str) -> Result<Self, Self::Err> {
Self::parse(text)
}
}
impl fmt::Display for Level {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::{Level, LevelError};
#[test]
fn ordering_runs_lowest_severity_first() {
let ladder = [
Level::Debug,
Level::Info,
Level::Notice,
Level::Warning,
Level::Error,
Level::Critical,
Level::Alert,
Level::Emergency,
];
for pair in ladder.windows(2) {
let [lower, higher] = pair else {
return;
};
assert!(lower < higher, "{lower} should order below {higher}");
}
}
#[test]
fn derived_order_agrees_with_derived_partial_order() {
for left in Level::ALL {
for right in Level::ALL {
assert_eq!(
left < right,
left.cmp(&right) == core::cmp::Ordering::Less,
"{left} vs {right}: `<` and `Ord` disagree"
);
}
}
}
#[test]
fn every_level_round_trips_through_its_spelling() {
for level in Level::ALL {
let text = level.as_str();
assert_eq!(Level::parse(text), Ok(level));
}
}
#[test]
fn an_unknown_spelling_is_refused_rather_than_guessed() {
assert!(matches!(Level::parse("Info"), Err(LevelError(_))));
assert!(matches!(Level::parse("verbose"), Err(LevelError(_))));
}
#[test]
fn all_covers_every_variant_in_order() {
assert_eq!(Level::ALL.len(), 8);
assert_eq!(Level::ALL.first(), Some(&Level::Debug));
assert_eq!(Level::ALL.last(), Some(&Level::Emergency));
}
}