diff --git a/metrics/CHANGELOG.md b/metrics/CHANGELOG.md index efed1e8e..e955722d 100644 --- a/metrics/CHANGELOG.md +++ b/metrics/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Added + +- Added a section to the crate-level documentation about `Metadata` and how it's used. +- Derived `Copy`, `PartialOrd` and `Ord` for `Metadata` to allow for cheap copies and the ability to compare levels for + filtering purposes. +- Added `TryFrom<&str>` for `Level` to allow parsing levels from strings. +- Updated the documentation for `Metadata` to better explain how it's used. + ## [0.24.0] - 2024-10-12 ### Added diff --git a/metrics/src/metadata.rs b/metrics/src/metadata.rs index 44308fea..a3d1d46e 100644 --- a/metrics/src/metadata.rs +++ b/metrics/src/metadata.rs @@ -1,5 +1,5 @@ -/// Describes the level of verbosity of a metric event. -#[derive(Debug, Clone, PartialEq, Eq)] +/// Verbosity of a metric. +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)] pub struct Level(LevelInner); impl Level { @@ -19,7 +19,22 @@ impl Level { pub const ERROR: Self = Self(LevelInner::Error); } -#[derive(Debug, Clone, PartialEq, Eq)] +impl std::convert::TryFrom<&str> for Level { + type Error = String; + + fn try_from(value: &str) -> Result { + match value.trim() { + "trace" | "TRACE" => Ok(Level::TRACE), + "debug" | "DEBUG" => Ok(Level::DEBUG), + "info" | "INFO" => Ok(Level::INFO), + "warn" | "WARN" => Ok(Level::WARN), + "error" | "ERROR" => Ok(Level::ERROR), + unknown => Err(format!("unknown log level: {} (expected one of 'trace', 'debug', 'info', 'warn', or 'error')", unknown)), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] enum LevelInner { Trace = 0, Debug = 1, @@ -44,7 +59,7 @@ enum LevelInner { /// /// Metadata usage is exporter-specific, and may be ignored entirely. See the documentation of the specific exporter /// being used for more information. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Metadata<'a> { target: &'a str, level: Level, @@ -77,3 +92,68 @@ impl<'a> Metadata<'a> { self.module_path } } + +#[cfg(test)] +mod tests { + use std::convert::TryFrom as _; + + use super::*; + + #[test] + fn level_try_from_valid() { + let cases = &[ + ("trace", Level::TRACE), ("TRACE", Level::TRACE), + ("debug", Level::DEBUG), ("DEBUG", Level::DEBUG), + ("info", Level::INFO), ("INFO", Level::INFO), + ("warn", Level::WARN), ("WARN", Level::WARN), + ("error", Level::ERROR), ("ERROR", Level::ERROR), + ]; + + for (input, expected) in cases { + assert_eq!(Level::try_from(*input).unwrap(), *expected); + + // Now try with some whitespace on either end. + let input_whitespace = format!(" {} ", input); + assert_eq!(Level::try_from(&*input_whitespace).unwrap(), *expected); + } + } + + #[test] + fn level_try_from_invalid() { + let cases = &["", "foo", "bar", "baz", "qux", "quux"]; + + for input in cases { + assert!(Level::try_from(*input).is_err()); + } + } + + #[test] + fn level_ordering() { + // A few manual comparisons because it makes me feel better: + assert!(Level::TRACE < Level::DEBUG); + assert!(Level::DEBUG < Level::INFO); + assert!(Level::ERROR > Level::DEBUG); + assert!(Level::WARN == Level::WARN); + + // Now check each level programmatically. + let levels = &[ + Level::TRACE, Level::DEBUG, Level::INFO, Level::WARN, Level::ERROR, + ]; + + for i in 0..levels.len() { + let current_level = levels[i]; + let lower_levels = &levels[..i]; + let higher_levels = &levels[i + 1..]; + + for lower_level in lower_levels { + assert!(current_level > *lower_level); + assert!(*lower_level < current_level); + } + + for higher_level in higher_levels { + assert!(current_level < *higher_level); + assert!(*higher_level > current_level); + } + } + } +}