Skip to main content

env_filter/
parser.rs

1use alloc::{borrow::ToOwned, format, string::String, vec::Vec};
2use core::fmt::{Display, Formatter};
3
4use log::LevelFilter;
5
6use crate::Directive;
7use crate::FilterOp;
8
9#[derive(#[automatically_derived]
impl ::core::default::Default for ParseResult {
    #[inline]
    fn default() -> ParseResult {
        ParseResult {
            directives: ::core::default::Default::default(),
            filter: ::core::default::Default::default(),
            errors: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl ::core::fmt::Debug for ParseResult {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "ParseResult",
            "directives", &self.directives, "filter", &self.filter, "errors",
            &&self.errors)
    }
}Debug)]
10pub(crate) struct ParseResult {
11    pub(crate) directives: Vec<Directive>,
12    pub(crate) filter: Option<FilterOp>,
13    pub(crate) errors: Vec<String>,
14}
15
16impl ParseResult {
17    fn add_directive(&mut self, directive: Directive) {
18        self.directives.push(directive);
19    }
20
21    fn set_filter(&mut self, filter: FilterOp) {
22        self.filter = Some(filter);
23    }
24
25    fn add_error(&mut self, message: String) {
26        self.errors.push(message);
27    }
28
29    pub(crate) fn ok(self) -> Result<(Vec<Directive>, Option<FilterOp>), ParseError> {
30        let Self {
31            directives,
32            filter,
33            errors,
34        } = self;
35        if let Some(error) = errors.into_iter().next() {
36            Err(ParseError { details: error })
37        } else {
38            Ok((directives, filter))
39        }
40    }
41}
42
43/// Error during logger directive parsing process.
44#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParseError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f, "ParseError",
            "details", &&self.details)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ParseError {
    #[inline]
    fn clone(&self) -> ParseError {
        ParseError { details: ::core::clone::Clone::clone(&self.details) }
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for ParseError {
    #[inline]
    fn eq(&self, other: &ParseError) -> bool { self.details == other.details }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ParseError {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<String>;
    }
}Eq, #[automatically_derived]
impl ::core::hash::Hash for ParseError {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.details, state)
    }
}Hash)]
45pub struct ParseError {
46    details: String,
47}
48
49impl Display for ParseError {
50    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
51        f.write_fmt(format_args!("error parsing logger filter: {0}", self.details))write!(f, "error parsing logger filter: {}", self.details)
52    }
53}
54
55#[cfg(feature = "std")]
56impl std::error::Error for ParseError {}
57
58/// Parse a logging specification string (e.g: `crate1,crate2::mod3,crate3::x=error/foo`)
59/// and return a vector with log directives.
60pub(crate) fn parse_spec(spec: &str) -> ParseResult {
61    let mut result = ParseResult::default();
62
63    let mut parts = spec.split('/');
64    let mods = parts.next();
65    let filter = parts.next();
66    if parts.next().is_some() {
67        result.add_error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid logging spec \'{0}\' (too many \'/\'s)",
                spec))
    })format!("invalid logging spec '{spec}' (too many '/'s)"));
68        return result;
69    }
70    if let Some(m) = mods {
71        for s in m.split(',').map(|ss| ss.trim()) {
72            if s.is_empty() {
73                continue;
74            }
75            let mut parts = s.split('=');
76            let (log_level, name) =
77                match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
78                    (Some(part0), None, None) => {
79                        // if the single argument is a log-level string or number,
80                        // treat that as a global fallback
81                        match part0.parse() {
82                            Ok(num) => (num, None),
83                            Err(_) => (LevelFilter::max(), Some(part0)),
84                        }
85                    }
86                    (Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)),
87                    (Some(part0), Some(part1), None) => {
88                        if let Ok(num) = part1.parse() {
89                            (num, Some(part0))
90                        } else {
91                            result.add_error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid logging spec \'{0}\'",
                part1))
    })format!("invalid logging spec '{part1}'"));
92                            continue;
93                        }
94                    }
95                    _ => {
96                        result.add_error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid logging spec \'{0}\'", s))
    })format!("invalid logging spec '{s}'"));
97                        continue;
98                    }
99                };
100
101            result.add_directive(Directive {
102                name: name.map(|s| s.to_owned()),
103                level: log_level,
104            });
105        }
106    }
107
108    if let Some(filter) = filter {
109        match FilterOp::new(filter) {
110            Ok(filter_op) => result.set_filter(filter_op),
111            Err(err) => result.add_error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid regex filter - {0}", err))
    })format!("invalid regex filter - {err}")),
112        }
113    }
114
115    result
116}
117
118#[cfg(test)]
119mod tests {
120    use crate::ParseError;
121    use log::LevelFilter;
122    use snapbox::{assert_data_eq, str, Data, IntoData};
123
124    use super::{parse_spec, ParseResult};
125
126    impl IntoData for ParseError {
127        fn into_data(self) -> Data {
128            self.to_string().into_data()
129        }
130    }
131
132    #[test]
133    fn parse_spec_valid() {
134        let ParseResult {
135            directives: dirs,
136            filter,
137            errors,
138        } = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
139
140        assert_eq!(dirs.len(), 3);
141        assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned()));
142        assert_eq!(dirs[0].level, LevelFilter::Error);
143
144        assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned()));
145        assert_eq!(dirs[1].level, LevelFilter::max());
146
147        assert_eq!(dirs[2].name, Some("crate2".to_owned()));
148        assert_eq!(dirs[2].level, LevelFilter::Debug);
149        assert!(filter.is_none());
150
151        assert!(errors.is_empty());
152    }
153
154    #[test]
155    fn parse_spec_invalid_crate() {
156        // test parse_spec with multiple = in specification
157        let ParseResult {
158            directives: dirs,
159            filter,
160            errors,
161        } = parse_spec("crate1::mod1=warn=info,crate2=debug");
162
163        assert_eq!(dirs.len(), 1);
164        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
165        assert_eq!(dirs[0].level, LevelFilter::Debug);
166        assert!(filter.is_none());
167
168        assert_eq!(errors.len(), 1);
169        assert_data_eq!(
170            &errors[0],
171            str!["invalid logging spec 'crate1::mod1=warn=info'"]
172        );
173    }
174
175    #[test]
176    fn parse_spec_invalid_level() {
177        // test parse_spec with 'noNumber' as log level
178        let ParseResult {
179            directives: dirs,
180            filter,
181            errors,
182        } = parse_spec("crate1::mod1=noNumber,crate2=debug");
183
184        assert_eq!(dirs.len(), 1);
185        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
186        assert_eq!(dirs[0].level, LevelFilter::Debug);
187        assert!(filter.is_none());
188
189        assert_eq!(errors.len(), 1);
190        assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]);
191    }
192
193    #[test]
194    fn parse_spec_string_level() {
195        // test parse_spec with 'warn' as log level
196        let ParseResult {
197            directives: dirs,
198            filter,
199            errors,
200        } = parse_spec("crate1::mod1=wrong,crate2=warn");
201
202        assert_eq!(dirs.len(), 1);
203        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
204        assert_eq!(dirs[0].level, LevelFilter::Warn);
205        assert!(filter.is_none());
206
207        assert_eq!(errors.len(), 1);
208        assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]);
209    }
210
211    #[test]
212    fn parse_spec_empty_level() {
213        // test parse_spec with '' as log level
214        let ParseResult {
215            directives: dirs,
216            filter,
217            errors,
218        } = parse_spec("crate1::mod1=wrong,crate2=");
219
220        assert_eq!(dirs.len(), 1);
221        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
222        assert_eq!(dirs[0].level, LevelFilter::max());
223        assert!(filter.is_none());
224
225        assert_eq!(errors.len(), 1);
226        assert_data_eq!(&errors[0], str!["invalid logging spec 'wrong'"]);
227    }
228
229    #[test]
230    fn parse_spec_empty_level_isolated() {
231        // test parse_spec with "" as log level (and the entire spec str)
232        let ParseResult {
233            directives: dirs,
234            filter,
235            errors,
236        } = parse_spec(""); // should be ignored
237        assert_eq!(dirs.len(), 0);
238        assert!(filter.is_none());
239        assert!(errors.is_empty());
240    }
241
242    #[test]
243    fn parse_spec_blank_level_isolated() {
244        // test parse_spec with a white-space-only string specified as the log
245        // level (and the entire spec str)
246        let ParseResult {
247            directives: dirs,
248            filter,
249            errors,
250        } = parse_spec("     "); // should be ignored
251        assert_eq!(dirs.len(), 0);
252        assert!(filter.is_none());
253        assert!(errors.is_empty());
254    }
255
256    #[test]
257    fn parse_spec_blank_level_isolated_comma_only() {
258        // The spec should contain zero or more comma-separated string slices,
259        // so a comma-only string should be interpreted as two empty strings
260        // (which should both be treated as invalid, so ignored).
261        let ParseResult {
262            directives: dirs,
263            filter,
264            errors,
265        } = parse_spec(","); // should be ignored
266        assert_eq!(dirs.len(), 0);
267        assert!(filter.is_none());
268        assert!(errors.is_empty());
269    }
270
271    #[test]
272    fn parse_spec_blank_level_isolated_comma_blank() {
273        // The spec should contain zero or more comma-separated string slices,
274        // so this bogus spec should be interpreted as containing one empty
275        // string and one blank string. Both should both be treated as
276        // invalid, so ignored.
277        let ParseResult {
278            directives: dirs,
279            filter,
280            errors,
281        } = parse_spec(",     "); // should be ignored
282        assert_eq!(dirs.len(), 0);
283        assert!(filter.is_none());
284        assert!(errors.is_empty());
285    }
286
287    #[test]
288    fn parse_spec_blank_level_isolated_blank_comma() {
289        // The spec should contain zero or more comma-separated string slices,
290        // so this bogus spec should be interpreted as containing one blank
291        // string and one empty string. Both should both be treated as
292        // invalid, so ignored.
293        let ParseResult {
294            directives: dirs,
295            filter,
296            errors,
297        } = parse_spec("     ,"); // should be ignored
298        assert_eq!(dirs.len(), 0);
299        assert!(filter.is_none());
300        assert!(errors.is_empty());
301    }
302
303    #[test]
304    fn parse_spec_global() {
305        // test parse_spec with no crate
306        let ParseResult {
307            directives: dirs,
308            filter,
309            errors,
310        } = parse_spec("warn,crate2=debug");
311        assert_eq!(dirs.len(), 2);
312        assert_eq!(dirs[0].name, None);
313        assert_eq!(dirs[0].level, LevelFilter::Warn);
314        assert_eq!(dirs[1].name, Some("crate2".to_owned()));
315        assert_eq!(dirs[1].level, LevelFilter::Debug);
316        assert!(filter.is_none());
317        assert!(errors.is_empty());
318    }
319
320    #[test]
321    fn parse_spec_global_bare_warn_lc() {
322        // test parse_spec with no crate, in isolation, all lowercase
323        let ParseResult {
324            directives: dirs,
325            filter,
326            errors,
327        } = parse_spec("warn");
328        assert_eq!(dirs.len(), 1);
329        assert_eq!(dirs[0].name, None);
330        assert_eq!(dirs[0].level, LevelFilter::Warn);
331        assert!(filter.is_none());
332        assert!(errors.is_empty());
333    }
334
335    #[test]
336    fn parse_spec_global_bare_warn_uc() {
337        // test parse_spec with no crate, in isolation, all uppercase
338        let ParseResult {
339            directives: dirs,
340            filter,
341            errors,
342        } = parse_spec("WARN");
343        assert_eq!(dirs.len(), 1);
344        assert_eq!(dirs[0].name, None);
345        assert_eq!(dirs[0].level, LevelFilter::Warn);
346        assert!(filter.is_none());
347        assert!(errors.is_empty());
348    }
349
350    #[test]
351    fn parse_spec_global_bare_warn_mixed() {
352        // test parse_spec with no crate, in isolation, mixed case
353        let ParseResult {
354            directives: dirs,
355            filter,
356            errors,
357        } = parse_spec("wArN");
358        assert_eq!(dirs.len(), 1);
359        assert_eq!(dirs[0].name, None);
360        assert_eq!(dirs[0].level, LevelFilter::Warn);
361        assert!(filter.is_none());
362        assert!(errors.is_empty());
363    }
364
365    #[test]
366    fn parse_spec_valid_filter() {
367        let ParseResult {
368            directives: dirs,
369            filter,
370            errors,
371        } = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
372        assert_eq!(dirs.len(), 3);
373        assert_eq!(dirs[0].name, Some("crate1::mod1".to_owned()));
374        assert_eq!(dirs[0].level, LevelFilter::Error);
375
376        assert_eq!(dirs[1].name, Some("crate1::mod2".to_owned()));
377        assert_eq!(dirs[1].level, LevelFilter::max());
378
379        assert_eq!(dirs[2].name, Some("crate2".to_owned()));
380        assert_eq!(dirs[2].level, LevelFilter::Debug);
381        assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
382        assert!(errors.is_empty());
383    }
384
385    #[test]
386    fn parse_spec_invalid_crate_filter() {
387        let ParseResult {
388            directives: dirs,
389            filter,
390            errors,
391        } = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
392
393        assert_eq!(dirs.len(), 1);
394        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
395        assert_eq!(dirs[0].level, LevelFilter::Debug);
396        assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
397
398        assert_eq!(errors.len(), 1);
399        assert_data_eq!(
400            &errors[0],
401            str!["invalid logging spec 'crate1::mod1=error=warn'"]
402        );
403    }
404
405    #[test]
406    fn parse_spec_empty_with_filter() {
407        let ParseResult {
408            directives: dirs,
409            filter,
410            errors,
411        } = parse_spec("crate1/a*c");
412        assert_eq!(dirs.len(), 1);
413        assert_eq!(dirs[0].name, Some("crate1".to_owned()));
414        assert_eq!(dirs[0].level, LevelFilter::max());
415        assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
416        assert!(errors.is_empty());
417    }
418
419    #[test]
420    fn parse_spec_with_multiple_filters() {
421        let ParseResult {
422            directives: dirs,
423            filter,
424            errors,
425        } = parse_spec("debug/abc/a.c");
426        assert!(dirs.is_empty());
427        assert!(filter.is_none());
428
429        assert_eq!(errors.len(), 1);
430        assert_data_eq!(
431            &errors[0],
432            str!["invalid logging spec 'debug/abc/a.c' (too many '/'s)"]
433        );
434    }
435
436    #[test]
437    fn parse_spec_multiple_invalid_crates() {
438        // test parse_spec with multiple = in specification
439        let ParseResult {
440            directives: dirs,
441            filter,
442            errors,
443        } = parse_spec("crate1::mod1=warn=info,crate2=debug,crate3=error=error");
444
445        assert_eq!(dirs.len(), 1);
446        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
447        assert_eq!(dirs[0].level, LevelFilter::Debug);
448        assert!(filter.is_none());
449
450        assert_eq!(errors.len(), 2);
451        assert_data_eq!(
452            &errors[0],
453            str!["invalid logging spec 'crate1::mod1=warn=info'"]
454        );
455        assert_data_eq!(
456            &errors[1],
457            str!["invalid logging spec 'crate3=error=error'"]
458        );
459    }
460
461    #[test]
462    fn parse_spec_multiple_invalid_levels() {
463        // test parse_spec with 'noNumber' as log level
464        let ParseResult {
465            directives: dirs,
466            filter,
467            errors,
468        } = parse_spec("crate1::mod1=noNumber,crate2=debug,crate3=invalid");
469
470        assert_eq!(dirs.len(), 1);
471        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
472        assert_eq!(dirs[0].level, LevelFilter::Debug);
473        assert!(filter.is_none());
474
475        assert_eq!(errors.len(), 2);
476        assert_data_eq!(&errors[0], str!["invalid logging spec 'noNumber'"]);
477        assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]);
478    }
479
480    #[test]
481    fn parse_spec_invalid_crate_and_level() {
482        // test parse_spec with 'noNumber' as log level
483        let ParseResult {
484            directives: dirs,
485            filter,
486            errors,
487        } = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid");
488
489        assert_eq!(dirs.len(), 1);
490        assert_eq!(dirs[0].name, Some("crate2".to_owned()));
491        assert_eq!(dirs[0].level, LevelFilter::Debug);
492        assert!(filter.is_none());
493
494        assert_eq!(errors.len(), 2);
495        assert_data_eq!(
496            &errors[0],
497            str!["invalid logging spec 'crate1::mod1=debug=info'"]
498        );
499        assert_data_eq!(&errors[1], str!["invalid logging spec 'invalid'"]);
500    }
501
502    #[test]
503    fn parse_error_message_single_error() {
504        let error = parse_spec("crate1::mod1=debug=info,crate2=debug")
505            .ok()
506            .unwrap_err();
507        assert_data_eq!(
508            error,
509            str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
510        );
511    }
512
513    #[test]
514    fn parse_error_message_multiple_errors() {
515        let error = parse_spec("crate1::mod1=debug=info,crate2=debug,crate3=invalid")
516            .ok()
517            .unwrap_err();
518        assert_data_eq!(
519            error,
520            str!["error parsing logger filter: invalid logging spec 'crate1::mod1=debug=info'"]
521        );
522    }
523}