Skip to content

Commit e0ad8cb

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 e0ad8cb

File tree

4 files changed

+102
-1
lines changed

4 files changed

+102
-1
lines changed

crates/env_filter/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
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

Lines changed: 37 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 60 additions & 1 deletion
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,36 @@ 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+
if self.errors.is_empty() {
30+
Ok((self.directives, self.filter))
31+
} else {
32+
Err(ParseError {
33+
details: self.errors[0],
34+
})
35+
}
36+
}
2537
}
2638

39+
/// Error during logger directive parsing process.
40+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
41+
pub struct ParseError {
42+
details: String,
43+
}
44+
45+
impl Display for ParseError {
46+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47+
write!(
48+
f,
49+
"error parsing logger filter: {}",
50+
self.details.join(", ")
51+
)
52+
}
53+
}
54+
55+
impl Error for ParseError {}
56+
2757
/// Parse a logging specification string (e.g: `crate1,crate2::mod3,crate3::x=error/foo`)
2858
/// and return a vector with log directives.
2959
pub(crate) fn parse_spec(spec: &str) -> ParseResult {
@@ -86,11 +116,18 @@ pub(crate) fn parse_spec(spec: &str) -> ParseResult {
86116

87117
#[cfg(test)]
88118
mod tests {
119+
use crate::ParseError;
89120
use log::LevelFilter;
90-
use snapbox::{assert_data_eq, str};
121+
use snapbox::{assert_data_eq, str, Data, IntoData};
91122

92123
use super::{parse_spec, ParseResult};
93124

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

0 commit comments

Comments
 (0)