Skip to content

Commit 05aacb9

Browse files
committed
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.
1 parent faf5b3e commit 05aacb9

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-1
lines changed

crates/env_filter/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77
<!-- next-header -->
88
## [Unreleased] - ReleaseDate
99

10+
### Features
11+
12+
- Added `env_filter::Builder::try_parse(&self, &str)` method (failable version of `env_filter::Builder::parse()`)
13+
1014
## [0.1.0] - 2024-01-19
1115

1216
<!-- next-url -->

crates/env_filter/src/filter.rs

+37
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::parse_spec;
99
use crate::parser::ParseResult;
1010
use crate::Directive;
1111
use crate::FilterOp;
12+
use crate::ParseError;
1213

1314
/// A builder for a log filter.
1415
///
@@ -118,6 +119,22 @@ impl Builder {
118119
self
119120
}
120121

122+
/// Parses the directive string, returning an error if the given directive string is invalid.
123+
///
124+
/// See the [Enabling Logging] section for more details.
125+
///
126+
/// [Enabling Logging]: ../index.html#enabling-logging
127+
pub fn try_parse(&mut self, filters: &str) -> Result<&mut Self, ParseError> {
128+
let (directives, filter) = parse_spec(filters).ok()?;
129+
130+
self.filter = filter;
131+
132+
for directive in directives {
133+
self.insert_directive(directive);
134+
}
135+
Ok(self)
136+
}
137+
121138
/// Build a log filter.
122139
pub fn build(&mut self) -> Filter {
123140
assert!(!self.built, "attempt to re-use consumed builder");
@@ -241,6 +258,7 @@ impl fmt::Debug for Filter {
241258
#[cfg(test)]
242259
mod tests {
243260
use log::{Level, LevelFilter};
261+
use snapbox::{assert_data_eq, str};
244262

245263
use super::{enabled, Builder, Directive, Filter};
246264

@@ -455,6 +473,25 @@ mod tests {
455473
}
456474
}
457475

476+
#[test]
477+
fn try_parse_valid_filter() {
478+
let logger = Builder::new()
479+
.try_parse("info,crate1::mod1=warn")
480+
.expect("valid filter returned error")
481+
.build();
482+
assert!(enabled(&logger.directives, Level::Warn, "crate1::mod1"));
483+
assert!(enabled(&logger.directives, Level::Info, "crate2::mod2"));
484+
}
485+
486+
#[test]
487+
fn try_parse_invalid_filter() {
488+
let error = Builder::new().try_parse("info,crate1=invalid").unwrap_err();
489+
assert_data_eq!(
490+
error,
491+
str!["error parsing logger filter: invalid logging spec 'invalid'"]
492+
);
493+
}
494+
458495
#[test]
459496
fn match_full_path() {
460497
let logger = make_logger_filter(vec![

crates/env_filter/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ use parser::parse_spec;
5656
pub use filter::Builder;
5757
pub use filter::Filter;
5858
pub use filtered_log::FilteredLog;
59+
pub use parser::ParseError;

crates/env_filter/src/parser.rs

+59-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use log::LevelFilter;
2+
use std::error::Error;
3+
use std::fmt::{Display, Formatter};
24

35
use crate::Directive;
46
use crate::FilterOp;
@@ -22,8 +24,35 @@ impl ParseResult {
2224
fn add_error(&mut self, message: String) {
2325
self.errors.push(message);
2426
}
27+
28+
pub(crate) fn ok(self) -> Result<(Vec<Directive>, Option<FilterOp>), ParseError> {
29+
let Self {
30+
directives,
31+
filter,
32+
errors,
33+
} = self;
34+
if let Some(error) = errors.into_iter().next() {
35+
Err(ParseError { details: error })
36+
} else {
37+
Ok((directives, filter))
38+
}
39+
}
40+
}
41+
42+
/// Error during logger directive parsing process.
43+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44+
pub struct ParseError {
45+
details: String,
46+
}
47+
48+
impl Display for ParseError {
49+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50+
write!(f, "error parsing logger filter: {}", self.details)
51+
}
2552
}
2653

54+
impl Error for ParseError {}
55+
2756
/// Parse a logging specification string (e.g: `crate1,crate2::mod3,crate3::x=error/foo`)
2857
/// and return a vector with log directives.
2958
pub(crate) fn parse_spec(spec: &str) -> ParseResult {
@@ -86,11 +115,18 @@ pub(crate) fn parse_spec(spec: &str) -> ParseResult {
86115

87116
#[cfg(test)]
88117
mod tests {
118+
use crate::ParseError;
89119
use log::LevelFilter;
90-
use snapbox::{assert_data_eq, str};
120+
use snapbox::{assert_data_eq, str, Data, IntoData};
91121

92122
use super::{parse_spec, ParseResult};
93123

124+
impl IntoData for ParseError {
125+
fn into_data(self) -> Data {
126+
self.to_string().into_data()
127+
}
128+
}
129+
94130
#[test]
95131
fn parse_spec_valid() {
96132
let ParseResult {
@@ -460,4 +496,26 @@ mod tests {
460496
);
461497
assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]);
462498
}
499+
500+
#[test]
501+
fn parse_error_message_single_error() {
502+
let error = parse_spec("crate1::mod1=debug=info,crate2=debug")
503+
.ok()
504+
.unwrap_err();
505+
assert_data_eq!(
506+
error,
507+
str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
508+
);
509+
}
510+
511+
#[test]
512+
fn parse_error_message_multiple_errors() {
513+
let error = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid")
514+
.ok()
515+
.unwrap_err();
516+
assert_data_eq!(
517+
error,
518+
str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
519+
);
520+
}
463521
}

0 commit comments

Comments
 (0)