1use anyhow::{Result, anyhow};
2
3pub struct EnvFilter {
4 pub level_global: Option<log::LevelFilter>,
5 pub directive_names: Vec<String>,
6 pub directive_levels: Vec<log::LevelFilter>,
7}
8
9pub fn parse(filter: &str) -> Result<EnvFilter> {
10 let mut max_level = None;
11 let mut directive_names = Vec::new();
12 let mut directive_levels = Vec::new();
13
14 for directive in filter.split(',') {
15 match directive.split_once('=') {
16 Some((name, level)) => {
17 if level.contains('=') {
18 return Err(anyhow!("Invalid directive: {}", directive));
19 }
20 let level = parse_level(level.trim())?;
21 directive_names.push(name.trim().trim_end_matches(".rs").to_string());
22 directive_levels.push(level);
23 }
24 None => {
25 let Ok(level) = parse_level(directive.trim()) else {
26 directive_names.push(directive.trim().trim_end_matches(".rs").to_string());
27 directive_levels.push(log::LevelFilter::max() /* Enable all levels */);
28 continue;
29 };
30 if max_level.is_some() {
31 return Err(anyhow!("Cannot set multiple max levels"));
32 }
33 max_level.replace(level);
34 }
35 };
36 }
37
38 Ok(EnvFilter {
39 level_global: max_level,
40 directive_names,
41 directive_levels,
42 })
43}
44
45fn parse_level(level: &str) -> Result<log::LevelFilter> {
46 if level.eq_ignore_ascii_case("TRACE") {
47 return Ok(log::LevelFilter::Trace);
48 }
49 if level.eq_ignore_ascii_case("DEBUG") {
50 return Ok(log::LevelFilter::Debug);
51 }
52 if level.eq_ignore_ascii_case("INFO") {
53 return Ok(log::LevelFilter::Info);
54 }
55 if level.eq_ignore_ascii_case("WARN") {
56 return Ok(log::LevelFilter::Warn);
57 }
58 if level.eq_ignore_ascii_case("ERROR") {
59 return Ok(log::LevelFilter::Error);
60 }
61 if level.eq_ignore_ascii_case("OFF") || level.eq_ignore_ascii_case("NONE") {
62 return Ok(log::LevelFilter::Off);
63 }
64 Err(anyhow!("Invalid level: {}", level))
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn global_level() {
73 let input = "info";
74 let filter = parse(input).unwrap();
75
76 assert_eq!(filter.level_global.unwrap(), log::LevelFilter::Info);
77 assert!(filter.directive_names.is_empty());
78 assert!(filter.directive_levels.is_empty());
79 }
80
81 #[test]
82 fn directive_level() {
83 let input = "my_module=debug";
84 let filter = parse(input).unwrap();
85
86 assert_eq!(filter.level_global, None);
87 assert_eq!(filter.directive_names, vec!["my_module".to_string()]);
88 assert_eq!(filter.directive_levels, vec![log::LevelFilter::Debug]);
89 }
90
91 #[test]
92 fn global_level_and_directive_level() {
93 let input = "info,my_module=debug";
94 let filter = parse(input).unwrap();
95
96 assert_eq!(filter.level_global.unwrap(), log::LevelFilter::Info);
97 assert_eq!(filter.directive_names, vec!["my_module".to_string()]);
98 assert_eq!(filter.directive_levels, vec![log::LevelFilter::Debug]);
99 }
100
101 #[test]
102 fn global_level_and_bare_module() {
103 let input = "info,my_module";
104 let filter = parse(input).unwrap();
105
106 assert_eq!(filter.level_global.unwrap(), log::LevelFilter::Info);
107 assert_eq!(filter.directive_names, vec!["my_module".to_string()]);
108 assert_eq!(filter.directive_levels, vec![log::LevelFilter::max()]);
109 }
110
111 #[test]
112 fn err_when_multiple_max_levels() {
113 let input = "info,warn";
114 let result = parse(input);
115
116 assert!(result.is_err());
117 }
118
119 #[test]
120 fn err_when_invalid_level() {
121 let input = "my_module=foobar";
122 let result = parse(input);
123
124 assert!(result.is_err());
125 }
126}