Skip to main content

logged_stream/
filter.rs

1use crate::Record;
2use crate::RecordKind;
3use itertools::Itertools;
4use std::fmt;
5
6//////////////////////////////////////////////////////////////////////////////////////////////////////////////
7// Trait
8//////////////////////////////////////////////////////////////////////////////////////////////////////////////
9
10/// Trait for filtering log records in [`LoggedStream`].
11///
12/// This trait allows filtering log records ([`Record`]) using the [`check`] method, which returns a [`bool`] value.
13/// It should be implemented for structures intended to be used as the filtering component within [`LoggedStream`].
14///
15/// [`check`]: RecordFilter::check
16/// [`LoggedStream`]: crate::LoggedStream
17pub trait RecordFilter: Send + 'static {
18    /// This method returns [`bool`] value depending on if received log record ([`Record`]) should be processed
19    /// by logging part inside [`LoggedStream`].
20    ///
21    /// [`LoggedStream`]: crate::LoggedStream
22    fn check(&self, record: &Record) -> bool;
23
24    /// This method provides [`fmt::Debug`] representation of the filter. It is used by composite filters
25    /// ([`AllFilter`] and [`AnyFilter`]) to produce detailed debug output of their underlying filters.
26    /// The default implementation outputs the type name as `"UnknownFilter"`.
27    /// Implementors are encouraged to override this method to provide meaningful debug information.
28    fn fmt_debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        f.write_str("UnknownFilter")
30    }
31}
32
33impl<T: RecordFilter + ?Sized> RecordFilter for Box<T> {
34    fn check(&self, record: &Record) -> bool {
35        (**self).check(record)
36    }
37
38    fn fmt_debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        (**self).fmt_debug(f)
40    }
41}
42
43//////////////////////////////////////////////////////////////////////////////////////////////////////////////
44// DefaultFilter
45//////////////////////////////////////////////////////////////////////////////////////////////////////////////
46
47/// This is default implementation of [`RecordFilter`] trait which [`check`] method always return `true`.
48/// It should be constructed using [`Default::default`] method.
49///
50/// [`check`]: RecordFilter::check
51#[derive(Debug, Clone, Copy, Default)]
52pub struct DefaultFilter;
53
54impl RecordFilter for DefaultFilter {
55    #[inline]
56    fn check(&self, _record: &Record) -> bool {
57        true
58    }
59
60    fn fmt_debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        fmt::Debug::fmt(self, f)
62    }
63}
64
65//////////////////////////////////////////////////////////////////////////////////////////////////////////////
66// RecordKindFilter
67//////////////////////////////////////////////////////////////////////////////////////////////////////////////
68
69/// Implementation of [`RecordFilter`] that accepts allowed [`RecordKind`] array.
70///
71/// This implementation of the [`RecordFilter`] trait accepts an array of allowed log record kinds ([`RecordKind`]) during
72/// construction. Its [`check`] method returns `true` if the received log record kind is present in this array.
73///
74/// [`check`]: RecordFilter::check
75#[derive(Debug)]
76pub struct RecordKindFilter {
77    allowed_kinds: Vec<RecordKind>,
78}
79
80impl RecordKindFilter {
81    /// Construct a new instance of [`RecordKindFilter`] using provided array of allowed log record kinds ([`RecordKind`]).
82    pub fn new(kinds: &'static [RecordKind]) -> Self {
83        Self {
84            allowed_kinds: kinds.iter().copied().unique().collect(),
85        }
86    }
87}
88
89impl RecordFilter for RecordKindFilter {
90    #[inline]
91    fn check(&self, record: &Record) -> bool {
92        self.allowed_kinds.contains(&record.kind)
93    }
94
95    fn fmt_debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        fmt::Debug::fmt(self, f)
97    }
98}
99
100//////////////////////////////////////////////////////////////////////////////////////////////////////////////
101// AllFilter
102//////////////////////////////////////////////////////////////////////////////////////////////////////////////
103
104/// Implementation of [`RecordFilter`] that combines multiple filters with AND logic.
105///
106/// This implementation of the [`RecordFilter`] trait accepts a vector of boxed filters during construction.
107/// Its [`check`] method returns `true` only if **all** underlying filters return `true` for the given record.
108/// If the filter list is empty, it returns `true` by default (empty conjunction is vacuously true).
109///
110/// This filter is useful for combining multiple filtering conditions where all must be satisfied.
111///
112/// # Examples
113///
114/// ```
115/// use logged_stream::{AllFilter, RecordKindFilter, RecordFilter, Record, RecordKind};
116///
117/// // Create a filter that accepts only Read operations
118/// let filter = AllFilter::new(vec![
119///     Box::new(RecordKindFilter::new(&[RecordKind::Read])),
120/// ]);
121///
122/// let read_record = Record::new(RecordKind::Read, String::from("data"));
123/// assert!(filter.check(&read_record));
124///
125/// let error_record = Record::new(RecordKind::Error, String::from("error"));
126/// assert!(!filter.check(&error_record));
127/// ```
128///
129/// [`check`]: RecordFilter::check
130pub struct AllFilter {
131    filters: Vec<Box<dyn RecordFilter>>,
132}
133
134impl fmt::Debug for AllFilter {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        f.debug_struct("AllFilter")
137            .field("filters", &RecordFiltersDebug(&self.filters))
138            .finish()
139    }
140}
141
142impl AllFilter {
143    /// Construct a new instance of [`AllFilter`] using provided vector of boxed filters.
144    ///
145    /// # Arguments
146    ///
147    /// * `filters` - A vector of boxed filters implementing [`RecordFilter`] trait
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use logged_stream::{AllFilter, RecordKindFilter, RecordKind};
153    ///
154    /// let filter = AllFilter::new(vec![
155    ///     Box::new(RecordKindFilter::new(&[RecordKind::Read])),
156    ///     Box::new(RecordKindFilter::new(&[RecordKind::Read, RecordKind::Write])),
157    /// ]);
158    /// ```
159    pub fn new(filters: Vec<Box<dyn RecordFilter>>) -> Self {
160        Self { filters }
161    }
162}
163
164impl RecordFilter for AllFilter {
165    #[inline]
166    fn check(&self, record: &Record) -> bool {
167        self.filters.iter().all(|filter| filter.check(record))
168    }
169
170    fn fmt_debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        fmt::Debug::fmt(self, f)
172    }
173}
174
175//////////////////////////////////////////////////////////////////////////////////////////////////////////////
176// AnyFilter
177//////////////////////////////////////////////////////////////////////////////////////////////////////////////
178
179/// Implementation of [`RecordFilter`] that combines multiple filters with OR logic.
180///
181/// This implementation of the [`RecordFilter`] trait accepts a vector of boxed filters during construction.
182/// Its [`check`] method returns `true` if **any** of the underlying filters returns `true` for the given record.
183/// If the filter list is empty, it returns `false` by default (empty disjunction is false).
184///
185/// This filter is useful for combining multiple filtering conditions where at least one must be satisfied.
186///
187/// # Examples
188///
189/// ```
190/// use logged_stream::{AnyFilter, RecordKindFilter, RecordFilter, Record, RecordKind};
191///
192/// // Create a filter that accepts Read OR Write operations
193/// let filter = AnyFilter::new(vec![
194///     Box::new(RecordKindFilter::new(&[RecordKind::Read])),
195///     Box::new(RecordKindFilter::new(&[RecordKind::Write])),
196/// ]);
197///
198/// let read_record = Record::new(RecordKind::Read, String::from("data"));
199/// assert!(filter.check(&read_record));
200///
201/// let write_record = Record::new(RecordKind::Write, String::from("data"));
202/// assert!(filter.check(&write_record));
203///
204/// let error_record = Record::new(RecordKind::Error, String::from("error"));
205/// assert!(!filter.check(&error_record));
206/// ```
207///
208/// [`check`]: RecordFilter::check
209pub struct AnyFilter {
210    filters: Vec<Box<dyn RecordFilter>>,
211}
212
213impl fmt::Debug for AnyFilter {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        f.debug_struct("AnyFilter")
216            .field("filters", &RecordFiltersDebug(&self.filters))
217            .finish()
218    }
219}
220
221impl AnyFilter {
222    /// Construct a new instance of [`AnyFilter`] using provided vector of boxed filters.
223    ///
224    /// # Arguments
225    ///
226    /// * `filters` - A vector of boxed filters implementing [`RecordFilter`] trait
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use logged_stream::{AnyFilter, RecordKindFilter, RecordKind};
232    ///
233    /// let filter = AnyFilter::new(vec![
234    ///     Box::new(RecordKindFilter::new(&[RecordKind::Read])),
235    ///     Box::new(RecordKindFilter::new(&[RecordKind::Write])),
236    /// ]);
237    /// ```
238    pub fn new(filters: Vec<Box<dyn RecordFilter>>) -> Self {
239        Self { filters }
240    }
241}
242
243impl RecordFilter for AnyFilter {
244    #[inline]
245    fn check(&self, record: &Record) -> bool {
246        self.filters.iter().any(|filter| filter.check(record))
247    }
248
249    fn fmt_debug(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        fmt::Debug::fmt(self, f)
251    }
252}
253
254//////////////////////////////////////////////////////////////////////////////////////////////////////////////
255// Debug Helpers
256//////////////////////////////////////////////////////////////////////////////////////////////////////////////
257
258/// Helper wrapper to bridge [`RecordFilter::fmt_debug`] into [`fmt::Debug`].
259struct RecordFilterDebug<'a>(&'a dyn RecordFilter);
260
261impl fmt::Debug for RecordFilterDebug<'_> {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        self.0.fmt_debug(f)
264    }
265}
266
267/// Helper wrapper to format a slice of [`RecordFilter`]s without allocating.
268struct RecordFiltersDebug<'a>(&'a [Box<dyn RecordFilter>]);
269
270impl fmt::Debug for RecordFiltersDebug<'_> {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        let mut list = f.debug_list();
273        for filter in self.0.iter() {
274            list.entry(&RecordFilterDebug(filter.as_ref()));
275        }
276        list.finish()
277    }
278}
279
280//////////////////////////////////////////////////////////////////////////////////////////////////////////////
281// Tests
282//////////////////////////////////////////////////////////////////////////////////////////////////////////////
283
284#[cfg(test)]
285mod tests {
286    use crate::filter::AllFilter;
287    use crate::filter::AnyFilter;
288    use crate::filter::DefaultFilter;
289    use crate::filter::RecordFilter;
290    use crate::filter::RecordKindFilter;
291    use crate::record::Record;
292    use crate::record::RecordKind;
293    use std::fmt;
294
295    fn assert_unpin<T: Unpin>() {}
296
297    #[test]
298    fn test_unpin() {
299        assert_unpin::<DefaultFilter>();
300        assert_unpin::<RecordKindFilter>();
301        assert_unpin::<AllFilter>();
302        assert_unpin::<AnyFilter>();
303    }
304
305    #[test]
306    fn test_default_filter() {
307        assert!(DefaultFilter.check(&Record::new(
308            RecordKind::Read,
309            String::from("01:02:03:04:05:06")
310        )));
311        assert!(DefaultFilter.check(&Record::new(
312            RecordKind::Write,
313            String::from("01:02:03:04:05:06")
314        )));
315        assert!(DefaultFilter.check(&Record::new(RecordKind::Drop, String::from("deallocated"))));
316        assert!(DefaultFilter.check(&Record::new(
317            RecordKind::Shutdown,
318            String::from("write shutdown request")
319        )));
320    }
321
322    #[test]
323    fn test_record_kind_filter() {
324        let filter = RecordKindFilter::new(&[RecordKind::Read]);
325        assert!(filter.check(&Record::new(
326            RecordKind::Read,
327            String::from("01:02:03:04:05:06")
328        )));
329        assert!(!filter.check(&Record::new(
330            RecordKind::Write,
331            String::from("01:02:03:04:05:06")
332        )));
333        assert!(!filter.check(&Record::new(RecordKind::Drop, String::from("deallocated"))));
334        assert!(!filter.check(&Record::new(
335            RecordKind::Shutdown,
336            String::from("write shutdown request")
337        )));
338    }
339
340    #[test]
341    fn test_all_filter_empty() {
342        let filter = AllFilter::new(vec![]);
343        // Empty conjunction should return true (vacuously true)
344        assert!(filter.check(&Record::new(RecordKind::Read, String::from("test"))));
345        assert!(filter.check(&Record::new(RecordKind::Write, String::from("test"))));
346        assert!(filter.check(&Record::new(RecordKind::Error, String::from("test"))));
347    }
348
349    #[test]
350    fn test_all_filter_single() {
351        let filter = AllFilter::new(vec![Box::new(RecordKindFilter::new(&[RecordKind::Read]))]);
352
353        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
354        assert!(!filter.check(&Record::new(RecordKind::Write, String::from("data"))));
355        assert!(!filter.check(&Record::new(RecordKind::Error, String::from("error"))));
356    }
357
358    #[test]
359    fn test_all_filter_multiple_all_pass() {
360        // Both filters accept Read
361        let filter = AllFilter::new(vec![
362            Box::new(RecordKindFilter::new(&[
363                RecordKind::Read,
364                RecordKind::Write,
365            ])),
366            Box::new(RecordKindFilter::new(&[
367                RecordKind::Read,
368                RecordKind::Error,
369            ])),
370        ]);
371
372        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
373    }
374
375    #[test]
376    fn test_all_filter_multiple_one_fails() {
377        // First accepts Write, second doesn't
378        let filter = AllFilter::new(vec![
379            Box::new(RecordKindFilter::new(&[
380                RecordKind::Read,
381                RecordKind::Write,
382            ])),
383            Box::new(RecordKindFilter::new(&[
384                RecordKind::Read,
385                RecordKind::Error,
386            ])),
387        ]);
388
389        assert!(!filter.check(&Record::new(RecordKind::Write, String::from("data"))));
390    }
391
392    #[test]
393    fn test_all_filter_multiple_all_fail() {
394        let filter = AllFilter::new(vec![
395            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
396            Box::new(RecordKindFilter::new(&[RecordKind::Write])),
397        ]);
398
399        assert!(!filter.check(&Record::new(RecordKind::Error, String::from("error"))));
400    }
401
402    #[test]
403    fn test_all_filter_with_default() {
404        // Combining with DefaultFilter (which always returns true)
405        let filter = AllFilter::new(vec![
406            Box::new(DefaultFilter),
407            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
408        ]);
409
410        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
411        assert!(!filter.check(&Record::new(RecordKind::Write, String::from("data"))));
412    }
413
414    #[test]
415    fn test_any_filter_empty() {
416        let filter = AnyFilter::new(vec![]);
417        // Empty disjunction should return false
418        assert!(!filter.check(&Record::new(RecordKind::Read, String::from("test"))));
419        assert!(!filter.check(&Record::new(RecordKind::Write, String::from("test"))));
420        assert!(!filter.check(&Record::new(RecordKind::Error, String::from("test"))));
421    }
422
423    #[test]
424    fn test_any_filter_single() {
425        let filter = AnyFilter::new(vec![Box::new(RecordKindFilter::new(&[RecordKind::Read]))]);
426
427        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
428        assert!(!filter.check(&Record::new(RecordKind::Write, String::from("data"))));
429        assert!(!filter.check(&Record::new(RecordKind::Error, String::from("error"))));
430    }
431
432    #[test]
433    fn test_any_filter_multiple_first_passes() {
434        let filter = AnyFilter::new(vec![
435            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
436            Box::new(RecordKindFilter::new(&[RecordKind::Write])),
437        ]);
438
439        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
440    }
441
442    #[test]
443    fn test_any_filter_multiple_second_passes() {
444        let filter = AnyFilter::new(vec![
445            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
446            Box::new(RecordKindFilter::new(&[RecordKind::Write])),
447        ]);
448
449        assert!(filter.check(&Record::new(RecordKind::Write, String::from("data"))));
450    }
451
452    #[test]
453    fn test_any_filter_multiple_all_pass() {
454        let filter = AnyFilter::new(vec![
455            Box::new(RecordKindFilter::new(&[
456                RecordKind::Read,
457                RecordKind::Write,
458            ])),
459            Box::new(RecordKindFilter::new(&[
460                RecordKind::Read,
461                RecordKind::Error,
462            ])),
463        ]);
464
465        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
466    }
467
468    #[test]
469    fn test_any_filter_multiple_all_fail() {
470        let filter = AnyFilter::new(vec![
471            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
472            Box::new(RecordKindFilter::new(&[RecordKind::Write])),
473        ]);
474
475        assert!(!filter.check(&Record::new(RecordKind::Error, String::from("error"))));
476    }
477
478    #[test]
479    fn test_any_filter_with_default() {
480        // Combining with DefaultFilter (which always returns true)
481        let filter = AnyFilter::new(vec![
482            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
483            Box::new(DefaultFilter),
484        ]);
485
486        // Should pass for everything because DefaultFilter always returns true
487        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
488        assert!(filter.check(&Record::new(RecordKind::Write, String::from("data"))));
489        assert!(filter.check(&Record::new(RecordKind::Error, String::from("error"))));
490    }
491
492    #[test]
493    fn test_nested_composite_filters() {
494        // (Read OR Write)
495        // Implemented as: AllFilter containing AnyFilter for (Read OR Write)
496        let filter = AllFilter::new(vec![Box::new(AnyFilter::new(vec![
497            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
498            Box::new(RecordKindFilter::new(&[RecordKind::Write])),
499        ]))]);
500
501        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
502        assert!(filter.check(&Record::new(RecordKind::Write, String::from("data"))));
503        assert!(!filter.check(&Record::new(RecordKind::Drop, String::from("dropped"))));
504        assert!(!filter.check(&Record::new(RecordKind::Error, String::from("error"))));
505    }
506
507    #[test]
508    fn test_complex_nested_filters() {
509        // AllFilter containing two AnyFilters:
510        // (Read OR Write) AND (Read OR Error)
511        // This should only pass for Read
512        let filter = AllFilter::new(vec![
513            Box::new(AnyFilter::new(vec![
514                Box::new(RecordKindFilter::new(&[RecordKind::Read])),
515                Box::new(RecordKindFilter::new(&[RecordKind::Write])),
516            ])),
517            Box::new(AnyFilter::new(vec![
518                Box::new(RecordKindFilter::new(&[RecordKind::Read])),
519                Box::new(RecordKindFilter::new(&[RecordKind::Error])),
520            ])),
521        ]);
522
523        assert!(filter.check(&Record::new(RecordKind::Read, String::from("data"))));
524        assert!(!filter.check(&Record::new(RecordKind::Write, String::from("data"))));
525        assert!(!filter.check(&Record::new(RecordKind::Error, String::from("error"))));
526        assert!(!filter.check(&Record::new(RecordKind::Drop, String::from("dropped"))));
527    }
528
529    #[test]
530    fn test_trait_object_safety() {
531        // Assert trait object construct.
532        let default: Box<dyn RecordFilter> = Box::<DefaultFilter>::default();
533        let record_kind: Box<dyn RecordFilter> = Box::new(RecordKindFilter::new(&[]));
534        let all: Box<dyn RecordFilter> = Box::new(AllFilter::new(vec![]));
535        let any: Box<dyn RecordFilter> = Box::new(AnyFilter::new(vec![]));
536
537        let record = Record::new(RecordKind::Open, String::from("test log record"));
538
539        // Assert that trait object methods are dispatchable.
540        _ = default.check(&record);
541        _ = record_kind.check(&record);
542        _ = all.check(&record);
543        _ = any.check(&record);
544    }
545
546    fn assert_record_filter<T: RecordFilter>() {}
547
548    #[test]
549    fn test_box() {
550        assert_record_filter::<Box<dyn RecordFilter>>();
551        assert_record_filter::<Box<RecordKindFilter>>();
552        assert_record_filter::<Box<DefaultFilter>>();
553        assert_record_filter::<Box<AllFilter>>();
554        assert_record_filter::<Box<AnyFilter>>();
555    }
556
557    fn assert_send<T: Send>() {}
558
559    #[test]
560    fn test_send() {
561        assert_send::<RecordKindFilter>();
562        assert_send::<DefaultFilter>();
563        assert_send::<AllFilter>();
564        assert_send::<AnyFilter>();
565
566        assert_send::<Box<dyn RecordFilter>>();
567        assert_send::<Box<RecordKindFilter>>();
568        assert_send::<Box<DefaultFilter>>();
569        assert_send::<Box<AllFilter>>();
570        assert_send::<Box<AnyFilter>>();
571    }
572
573    fn assert_debug<T: fmt::Debug>() {}
574
575    #[test]
576    fn test_debug() {
577        assert_debug::<DefaultFilter>();
578        assert_debug::<RecordKindFilter>();
579        assert_debug::<AllFilter>();
580        assert_debug::<AnyFilter>();
581
582        assert_debug::<Box<DefaultFilter>>();
583        assert_debug::<Box<RecordKindFilter>>();
584        assert_debug::<Box<AllFilter>>();
585        assert_debug::<Box<AnyFilter>>();
586    }
587
588    #[test]
589    fn test_debug_output() {
590        // DefaultFilter
591        assert_eq!(format!("{:?}", DefaultFilter), "DefaultFilter");
592
593        // RecordKindFilter
594        let record_kind_filter = RecordKindFilter::new(&[RecordKind::Read, RecordKind::Write]);
595        let debug_str = format!("{:?}", record_kind_filter);
596        assert!(debug_str.contains("RecordKindFilter"));
597        assert!(debug_str.contains("Read"));
598        assert!(debug_str.contains("Write"));
599
600        // AllFilter (empty)
601        let all_filter_empty = AllFilter::new(vec![]);
602        let debug_str = format!("{:?}", all_filter_empty);
603        assert!(debug_str.contains("AllFilter"));
604        assert!(debug_str.contains("filters: []"));
605
606        // AllFilter (with children)
607        let all_filter = AllFilter::new(vec![
608            Box::new(RecordKindFilter::new(&[RecordKind::Read])),
609            Box::new(DefaultFilter),
610        ]);
611        let debug_str = format!("{:?}", all_filter);
612        assert!(debug_str.contains("AllFilter"));
613        assert!(debug_str.contains("RecordKindFilter"));
614        assert!(debug_str.contains("Read"));
615        assert!(debug_str.contains("DefaultFilter"));
616
617        // AnyFilter (empty)
618        let any_filter_empty = AnyFilter::new(vec![]);
619        let debug_str = format!("{:?}", any_filter_empty);
620        assert!(debug_str.contains("AnyFilter"));
621        assert!(debug_str.contains("filters: []"));
622
623        // AnyFilter (with children)
624        let any_filter = AnyFilter::new(vec![
625            Box::new(RecordKindFilter::new(&[RecordKind::Write])),
626            Box::new(DefaultFilter),
627        ]);
628        let debug_str = format!("{:?}", any_filter);
629        assert!(debug_str.contains("AnyFilter"));
630        assert!(debug_str.contains("RecordKindFilter"));
631        assert!(debug_str.contains("Write"));
632        assert!(debug_str.contains("DefaultFilter"));
633
634        // Nested composite filter
635        let nested = AllFilter::new(vec![Box::new(AnyFilter::new(vec![Box::new(
636            RecordKindFilter::new(&[RecordKind::Read]),
637        )]))]);
638        let debug_str = format!("{:?}", nested);
639        assert!(debug_str.contains("AllFilter"));
640        assert!(debug_str.contains("AnyFilter"));
641        assert!(debug_str.contains("RecordKindFilter"));
642        assert!(debug_str.contains("Read"));
643    }
644}