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#[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
58pub(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 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 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 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 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 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 let ParseResult {
233 directives: dirs,
234 filter,
235 errors,
236 } = parse_spec(""); 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 let ParseResult {
247 directives: dirs,
248 filter,
249 errors,
250 } = parse_spec(" "); 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 let ParseResult {
262 directives: dirs,
263 filter,
264 errors,
265 } = parse_spec(","); 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 let ParseResult {
278 directives: dirs,
279 filter,
280 errors,
281 } = parse_spec(", "); 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 let ParseResult {
294 directives: dirs,
295 filter,
296 errors,
297 } = parse_spec(" ,"); 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 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 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 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 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 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 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 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}