parse_env_filter/
lazy.rs

1use crate::ParseError;
2
3/// Parse a series of filters out of a directive string.
4///
5/// Note that this is a lazy operation, including validation; parsing/validation
6/// are done simultaneously and on demand in zero-alloc streaming fashion.
7pub fn filters(directives: &str) -> Filters<'_> {
8    Filters { directives }
9}
10
11/// Parser-iterator of [Filter]s.
12#[derive(Debug, Clone)]
13pub struct Filters<'a> {
14    directives: &'a str,
15}
16
17/// A single event filter, `target[span{field=value}]=level`.
18///
19/// Span directives are not parsed/validated until pulled.
20#[derive(Debug, Clone)]
21pub struct Filter<'a> {
22    pub target: &'a str,
23    pub span: Option<SpanFilters<'a>>,
24    pub level: Option<&'a str>,
25}
26
27/// Parser-iterator of [SpanFilter]s.
28#[derive(Debug, Clone)]
29pub struct SpanFilters<'a> {
30    directives: &'a str,
31}
32
33/// A single span filter, `[span{field=value}]`.
34///
35/// Field directives are not parsed/validated until pulled.
36#[derive(Debug, Clone)]
37pub struct SpanFilter<'a> {
38    pub name: &'a str,
39    pub fields: Option<FieldFilters<'a>>,
40}
41
42/// Parser-iterator of [FieldFilter]s.
43#[derive(Debug, Clone)]
44pub struct FieldFilters<'a> {
45    directives: &'a str,
46}
47
48/// A single field filter, `{field=value}`.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct FieldFilter<'a> {
51    pub name: &'a str,
52    pub value: Option<&'a str>,
53}
54
55#[repr(u8)]
56#[derive(Clone, Copy)]
57enum Syntax {
58    LBrack = b'[',
59    RBrack = b']',
60    LBrace = b'{',
61    RBrace = b'}',
62    Equal = b'=',
63    Comma = b',',
64}
65
66fn find_any_syntax(haystack: &str) -> (usize, Option<Syntax>) {
67    use Syntax::*;
68    haystack
69        .bytes()
70        .enumerate()
71        .find_map(|(i, b)| match b {
72            b'[' => Some((i, LBrack)),
73            b']' => Some((i, RBrack)),
74            b'{' => Some((i, LBrace)),
75            b'}' => Some((i, RBrace)),
76            b'=' => Some((i, Equal)),
77            b',' => Some((i, Comma)),
78            _ => None,
79        })
80        .map_or_else(|| (haystack.len(), None), |(i, c)| (i, Some(c)))
81}
82
83// % represents end-of-text
84macro_rules! switch_syntax {
85    ($haystack:expr => |$i:ident| {
86        $($($syntax:tt)|+ => $expr:expr),* $(,)?
87    }) => {
88        #[allow(unused_variables)]
89        match find_any_syntax($haystack) {
90            $(($i, $(switch_syntax!(@syntax $syntax))|+) => $expr,)*
91        }
92    };
93
94    (@syntax '[') => (Some(Syntax::LBrack));
95    (@syntax ']') => (Some(Syntax::RBrack));
96    (@syntax '{') => (Some(Syntax::LBrace));
97    (@syntax '}') => (Some(Syntax::RBrace));
98    (@syntax '=') => (Some(Syntax::Equal));
99    (@syntax ',') => (Some(Syntax::Comma));
100    (@syntax  % ) => (None);
101}
102
103fn find_syntax(haystack: &str, syntax: Syntax) -> Option<usize> {
104    haystack.bytes().position(move |b| b == syntax as u8)
105}
106
107impl<'a> Filters<'a> {
108    fn err<T>(&mut self) -> Result<T, ParseError> {
109        self.directives = "";
110        Err(ParseError::BadSyntax)
111    }
112
113    fn target(&mut self) -> Result<&'a str, ParseError> {
114        switch_syntax!(self.directives => |i| {
115            // target]
116            // target{
117            // target}
118            //       👆
119            ']' | '{' | '}' => self.err(),
120
121            // target[
122            // target=
123            // target,
124            // target%
125            //       👆
126            '[' | '=' | ',' | % => {
127                let target = &self.directives[..i];
128                self.directives = &self.directives[i..];
129                Ok(target)
130            },
131        })
132    }
133
134    fn span(&mut self) -> Result<Option<SpanFilters<'a>>, ParseError> {
135        // at this point, we know directive starts with one of `[=,%`
136        if let Some(stripped) = self.directives.strip_prefix('[') {
137            self.directives = stripped;
138            match find_syntax(self.directives, Syntax::RBrack) {
139                None => self.err(),
140                // span]
141                //     👆
142                Some(i) => {
143                    let directives = &self.directives[..i];
144                    self.directives = &self.directives[i + 1..];
145                    Ok(Some(SpanFilters { directives }))
146                }
147            }
148        } else {
149            Ok(None)
150        }
151    }
152
153    fn level(&mut self) -> Result<Option<&'a str>, ParseError> {
154        // validate we have no junk after span directive
155        if self.directives.is_empty() || self.directives.starts_with(',') {
156            return Ok(None);
157        }
158        if let Some(stripped) = self.directives.strip_prefix('=') {
159            self.directives = stripped;
160        } else {
161            return self.err();
162        }
163        switch_syntax!(self.directives => |i| {
164            // level[
165            // level]
166            // level{
167            // level}
168            // level=
169            //      👆
170            '[' | ']' | '{' | '}' | '=' => self.err(),
171
172            // level,
173            // level%
174            //      👆
175            ',' | % => {
176                let level = &self.directives[..i];
177                self.directives = &self.directives[i..];
178                Ok(Some(level))
179            },
180        })
181    }
182
183    fn comma(&mut self) -> Result<(), ParseError> {
184        if let Some(stripped) = self.directives.strip_prefix(',') {
185            self.directives = stripped;
186            Ok(())
187        } else if self.directives.is_empty() {
188            Ok(())
189        } else {
190            self.err()
191        }
192    }
193}
194
195impl<'a> Iterator for Filters<'a> {
196    type Item = Result<Filter<'a>, ParseError>;
197
198    fn next(&mut self) -> Option<Self::Item> {
199        if self.directives.is_empty() {
200            return None;
201        }
202
203        // Reserved syntax
204        if self.directives.contains('"') || self.directives.contains('/') {
205            let _ = self.err::<()>();
206            return Some(Err(ParseError::ReservedSyntax));
207        }
208
209        Some((|| {
210            let target = self.target()?;
211            let span = self.span()?;
212            let level = self.level()?;
213            self.comma()?;
214            Ok(Filter {
215                target,
216                span,
217                level,
218            })
219        })())
220    }
221
222    fn size_hint(&self) -> (usize, Option<usize>) {
223        (
224            0,
225            Some(
226                self.directives
227                    .as_bytes()
228                    .iter()
229                    .filter(|&&b| b == b',')
230                    .count()
231                    + !self.directives.is_empty() as usize,
232            ),
233        )
234    }
235}
236
237impl<'a> SpanFilters<'a> {
238    fn err<T>(&mut self) -> Result<T, ParseError> {
239        self.directives = "";
240        Err(ParseError::BadSyntax)
241    }
242
243    fn name(&mut self) -> Result<&'a str, ParseError> {
244        switch_syntax!(self.directives => |i| {
245            // span[
246            // span]
247            // span}
248            // span=
249            //     👆
250            '[' | ']' | '}' | '=' => self.err(),
251
252            // span{
253            // span,
254            // span%
255            //     👆
256            '{' | ',' | % => {
257                let name = &self.directives[..i];
258                self.directives = &self.directives[i..];
259                Ok(name)
260            },
261        })
262    }
263
264    fn fields(&mut self) -> Result<Option<FieldFilters<'a>>, ParseError> {
265        // at this point, we know directive starts with one of `{,%`
266        if let Some(stripped) = self.directives.strip_prefix('{') {
267            self.directives = stripped;
268            match find_syntax(self.directives, Syntax::RBrace) {
269                None => self.err(),
270                // field}
271                //      👆
272                Some(i) => {
273                    let directives = &self.directives[..i];
274                    self.directives = &self.directives[i + 1..];
275                    Ok(Some(FieldFilters { directives }))
276                }
277            }
278        } else {
279            Ok(None)
280        }
281    }
282
283    fn comma(&mut self) -> Result<(), ParseError> {
284        if let Some(stripped) = self.directives.strip_prefix(',') {
285            self.directives = stripped;
286            Ok(())
287        } else if self.directives.is_empty() {
288            Ok(())
289        } else {
290            self.err()
291        }
292    }
293}
294
295impl<'a> Iterator for SpanFilters<'a> {
296    type Item = Result<SpanFilter<'a>, ParseError>;
297
298    fn next(&mut self) -> Option<Self::Item> {
299        if self.directives.is_empty() {
300            return None;
301        }
302
303        // Reserved syntax
304        if self.directives.contains('"') || self.directives.contains('/') {
305            let _ = self.err::<()>();
306            return Some(Err(ParseError::ReservedSyntax));
307        }
308
309        Some((|| {
310            let name = self.name()?;
311            let fields = self.fields()?;
312            self.comma()?;
313            Ok(SpanFilter { name, fields })
314        })())
315    }
316
317    fn size_hint(&self) -> (usize, Option<usize>) {
318        (
319            0,
320            Some(
321                self.directives
322                    .as_bytes()
323                    .iter()
324                    .filter(|&&b| b == b',')
325                    .count()
326                    + !self.directives.is_empty() as usize,
327            ),
328        )
329    }
330}
331
332impl<'a> FieldFilters<'a> {
333    fn err<T>(&mut self) -> Result<T, ParseError> {
334        self.directives = "";
335        Err(ParseError::BadSyntax)
336    }
337
338    fn name(&mut self) -> Result<&'a str, ParseError> {
339        switch_syntax!(self.directives => |i| {
340            // field[
341            // field]
342            // field{
343            // field}
344            //      👆
345            '[' | ']' | '{' | '}' => self.err(),
346
347            // field=
348            // field,
349            // field%
350            //      👆
351            '=' | ',' | % => {
352                let name = &self.directives[..i];
353                self.directives = &self.directives[i..];
354                Ok(name)
355            },
356        })
357    }
358
359    fn value(&mut self) -> Result<Option<&'a str>, ParseError> {
360        // at this point, we know directive starts with one of `=,%`
361        if let Some(stripped) = self.directives.strip_prefix('=') {
362            self.directives = stripped;
363        } else {
364            return Ok(None);
365        }
366        switch_syntax!(self.directives => |i| {
367            // value[
368            // value]
369            // value{
370            // value}
371            // value=
372            //      👆
373            '[' | ']' | '{' | '}' | '=' => self.err(),
374
375            // value,
376            // value%
377            //      👆
378            ',' | % => {
379                let value = &self.directives[..i];
380                self.directives = &self.directives[i..];
381                Ok(Some(value))
382            },
383        })
384    }
385
386    fn comma(&mut self) -> Result<(), ParseError> {
387        if let Some(stripped) = self.directives.strip_prefix(',') {
388            self.directives = stripped;
389            Ok(())
390        } else if self.directives.is_empty() {
391            Ok(())
392        } else {
393            self.err()
394        }
395    }
396}
397
398impl<'a> Iterator for FieldFilters<'a> {
399    type Item = Result<FieldFilter<'a>, ParseError>;
400
401    fn next(&mut self) -> Option<Self::Item> {
402        if self.directives.is_empty() {
403            return None;
404        }
405
406        // Reserved syntax
407        if self.directives.contains('"') || self.directives.contains('/') {
408            let _ = self.err::<()>();
409            return Some(Err(ParseError::ReservedSyntax));
410        }
411
412        Some((|| {
413            let name = self.name()?;
414            let value = self.value()?;
415            self.comma()?;
416            Ok(FieldFilter { name, value })
417        })())
418    }
419
420    fn size_hint(&self) -> (usize, Option<usize>) {
421        (
422            0,
423            Some(
424                self.directives
425                    .as_bytes()
426                    .iter()
427                    .filter(|&&b| b == b',')
428                    .count()
429                    + !self.directives.is_empty() as usize,
430            ),
431        )
432    }
433}