From 700eff0f5d5b2491b0153eac24a255161ac5c4e5 Mon Sep 17 00:00:00 2001 From: Maxim Date: Sat, 20 Jul 2024 23:55:42 +0900 Subject: [PATCH] feat: Add Builder::try_parse method Current implementation of the Builder::parse() method prints out specification errors to stderr and then just ignores them. This is fine for most console applications, but in some cases better control over what is happening is needed: * Sometimes there is no access to stderr, in that case there is no way to find out what went wrong. * For some applications it's more desirable to fail on startup than to run with (partially) invalid configuration. For such cases this commit introduces a new method try_parse that does the same thing as the parse method, but returns an Err in case an error in the specification is found. --- crates/env_filter/CHANGELOG.md | 8 +++++ crates/env_filter/src/filter.rs | 37 ++++++++++++++++++++ crates/env_filter/src/lib.rs | 1 + crates/env_filter/src/parser.rs | 61 ++++++++++++++++++++++++++++++++- 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/crates/env_filter/CHANGELOG.md b/crates/env_filter/CHANGELOG.md index b9a276c..c58a24f 100644 --- a/crates/env_filter/CHANGELOG.md +++ b/crates/env_filter/CHANGELOG.md @@ -1,14 +1,22 @@ # Change Log + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + ## [Unreleased] - ReleaseDate +### Features + +- Added `env_filter::Builder::try_parse(&self, &str)` method (failable version of `env_filter::Builder::parse()`) + ## [0.1.0] - 2024-01-19 + [Unreleased]: https://github.com/rust-cli/env_logger/compare/env_filter-v0.1.0...HEAD + [0.1.0]: https://github.com/rust-cli/env_logger/compare/b4a2c304c16d1db4a2998f24c00e00c0f776113b...env_filter-v0.1.0 diff --git a/crates/env_filter/src/filter.rs b/crates/env_filter/src/filter.rs index f0f860f..fa28ebd 100644 --- a/crates/env_filter/src/filter.rs +++ b/crates/env_filter/src/filter.rs @@ -9,6 +9,7 @@ use crate::parse_spec; use crate::parser::ParseResult; use crate::Directive; use crate::FilterOp; +use crate::ParseError; /// A builder for a log filter. /// @@ -118,6 +119,22 @@ impl Builder { self } + /// Parses the directive string, returning an error if the given directive string is invalid. + /// + /// See the [Enabling Logging] section for more details. + /// + /// [Enabling Logging]: ../index.html#enabling-logging + pub fn try_parse(&mut self, filters: &str) -> Result<&mut Self, ParseError> { + let (directives, filter) = parse_spec(filters).ok()?; + + self.filter = filter; + + for directive in directives { + self.insert_directive(directive); + } + Ok(self) + } + /// Build a log filter. pub fn build(&mut self) -> Filter { assert!(!self.built, "attempt to re-use consumed builder"); @@ -241,6 +258,7 @@ impl fmt::Debug for Filter { #[cfg(test)] mod tests { use log::{Level, LevelFilter}; + use snapbox::{assert_data_eq, str}; use super::{enabled, Builder, Directive, Filter}; @@ -455,6 +473,25 @@ mod tests { } } + #[test] + fn try_parse_valid_filter() { + let logger = Builder::new() + .try_parse("info,crate1::mod1=warn") + .expect("valid filter returned error") + .build(); + assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1")); + assert!(enabled(&logger.directives, Level::Info, "crate2::mod2")); + } + + #[test] + fn try_parse_invalid_filter() { + let error = Builder::new().try_parse("info,crate1=invalid").unwrap_err(); + assert_data_eq!( + error, + str!["error parsing logger filter: invalid logging spec 'invalid'"] + ); + } + #[test] fn match_full_path() { let logger = make_logger_filter(vec![ diff --git a/crates/env_filter/src/lib.rs b/crates/env_filter/src/lib.rs index 6a72204..e9f42d1 100644 --- a/crates/env_filter/src/lib.rs +++ b/crates/env_filter/src/lib.rs @@ -56,3 +56,4 @@ use parser::parse_spec; pub use filter::Builder; pub use filter::Filter; pub use filtered_log::FilteredLog; +pub use parser::ParseError; diff --git a/crates/env_filter/src/parser.rs b/crates/env_filter/src/parser.rs index b957e4a..6ede6d6 100644 --- a/crates/env_filter/src/parser.rs +++ b/crates/env_filter/src/parser.rs @@ -1,4 +1,6 @@ use log::LevelFilter; +use std::error::Error; +use std::fmt::{Display, Formatter}; use crate::Directive; use crate::FilterOp; @@ -22,8 +24,36 @@ impl ParseResult { fn add_error(&mut self, message: String) { self.errors.push(message); } + + pub(crate) fn ok(self) -> Result<(Vec, Option), ParseError> { + if self.errors.is_empty() { + Ok((self.directives, self.filter)) + } else { + Err(ParseError { + details: self.errors, + }) + } + } } +/// Error during logger directive parsing process. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParseError { + details: Vec, +} + +impl Display for ParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "error parsing logger filter: {}", + self.details.join(", ") + ) + } +} + +impl Error for ParseError {} + /// Parse a logging specification string (e.g: `crate1,crate2::mod3,crate3::x=error/foo`) /// and return a vector with log directives. pub(crate) fn parse_spec(spec: &str) -> ParseResult { @@ -86,11 +116,18 @@ pub(crate) fn parse_spec(spec: &str) -> ParseResult { #[cfg(test)] mod tests { + use crate::ParseError; use log::LevelFilter; - use snapbox::{assert_data_eq, str}; + use snapbox::{assert_data_eq, str, Data, IntoData}; use super::{parse_spec, ParseResult}; + impl IntoData for ParseError { + fn into_data(self) -> Data { + self.to_string().into_data() + } + } + #[test] fn parse_spec_valid() { let ParseResult { @@ -460,4 +497,26 @@ mod tests { ); assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]); } + + #[test] + fn parse_error_message_single_error() { + let error = parse_spec("crate1::mod1=debug=info,crate2=debug") + .ok() + .unwrap_err(); + assert_data_eq!( + error, + str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"] + ); + } + + #[test] + fn parse_error_message_multiple_errors() { + let error = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid") + .ok() + .unwrap_err(); + assert_data_eq!( + error, + str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info', invalid logging spec 'invalid'"] + ); + } }