1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Extension trait for asserting against collections of `CapturedEvent`s and `CapturedSpan`s.

use predicates::Predicate;

use std::fmt;

use crate::{CapturedEvent, CapturedEvents, CapturedSpan, CapturedSpans, DescendantSpans, Storage};

/// Helper to wrap holders of [`CapturedSpan`]s or [`CapturedEvent`]s
/// (spans or the underlying [`Storage`]) so that they are more convenient to use with `Predicate`s.
///
/// See [the module-level docs](crate::predicates) for examples of usage.
///
/// [`CapturedEvent`]: crate::CapturedEvent
pub trait ScanExt<'a>: Sized {
    /// Creates a scanner for the spans contained by this holder. What is meant by "contained"
    /// (i.e., whether the scan is deep or shallow), depends on the holder type and is documented
    /// at the corresponding impl.
    fn scan_spans(self) -> Scanner<Self, CapturedSpans<'a>>;
    /// Creates a scanner for the events contained by this holder. What is meant by "contained"
    /// (i.e., whether the scan is deep or shallow), depends on the holder type and is documented
    /// at the corresponding impl.
    fn scan_events(self) -> Scanner<Self, CapturedEvents<'a>>;
}

/// Scans for `Storage` are deep; they include *all* captured spans / events, not just root ones.
impl<'a> ScanExt<'a> for &'a Storage {
    fn scan_spans(self) -> Scanner<Self, CapturedSpans<'a>> {
        Scanner::new(self, Storage::all_spans)
    }

    fn scan_events(self) -> Scanner<Self, CapturedEvents<'a>> {
        Scanner::new(self, Storage::all_events)
    }
}

/// Scans for `CapturedSpan` are shallow, i.e. include only direct children spans / events.
impl<'a> ScanExt<'a> for CapturedSpan<'a> {
    fn scan_spans(self) -> Scanner<Self, CapturedSpans<'a>> {
        Scanner::new(self, |span| span.children())
    }

    fn scan_events(self) -> Scanner<Self, CapturedEvents<'a>> {
        Scanner::new(self, |span| span.events())
    }
}

impl<'a> CapturedSpan<'a> {
    /// Deeply scans all descendants of this span.
    pub fn deep_scan_spans(self) -> Scanner<Self, DescendantSpans<'a>> {
        Scanner::new(self, |span| span.descendants())
    }

    /// Deeply scans all descendant events of this span.
    pub fn deep_scan_events(self) -> Scanner<Self, impl Iterator<Item = CapturedEvent<'a>> + 'a> {
        Scanner::new(self, |span| span.events().chain(span.descendant_events()))
    }
}

/// Helper that allows using `Predicate`s rather than closures to find matching elements,
/// and provides more informative error messages.
///
/// Returned by the [`ScanExt`] methods; see its docs for more details.
#[derive(Debug)]
pub struct Scanner<T, I> {
    items: T,
    into_iter: fn(T) -> I,
}

impl<T: Clone, I> Clone for Scanner<T, I> {
    fn clone(&self) -> Self {
        Self {
            items: self.items.clone(),
            into_iter: self.into_iter,
        }
    }
}

impl<T: Copy, I> Copy for Scanner<T, I> {}

impl<T, I> Scanner<T, I>
where
    I: Iterator,
    I::Item: fmt::Debug,
{
    fn new(items: T, into_iter: fn(T) -> I) -> Self {
        Self { items, into_iter }
    }

    fn iter(self) -> I {
        (self.into_iter)(self.items)
    }

    /// Finds the single item matching the predicate.
    ///
    /// # Panics
    ///
    /// Panics with an informative message if no items, or multiple items match the predicate.
    pub fn single<P: Predicate<I::Item> + ?Sized>(self, predicate: &P) -> I::Item {
        let mut iter = self.iter();
        let first = iter
            .find(|item| predicate.eval(item))
            .unwrap_or_else(|| panic!("no items have matched predicate {predicate}"));

        let second = iter.find(|item| predicate.eval(item));
        if let Some(second) = second {
            panic!(
                "multiple items match predicate {predicate}: {:#?}",
                [first, second]
            );
        }
        first
    }

    /// Finds the first item matching the predicate.
    ///
    /// # Panics
    ///
    /// Panics with an informative message if no items match the predicate.
    pub fn first<P: Predicate<I::Item> + ?Sized>(self, predicate: &P) -> I::Item {
        let mut iter = self.iter();
        iter.find(|item| predicate.eval(item))
            .unwrap_or_else(|| panic!("no items have matched predicate {predicate}"))
    }

    /// Checks that all of the items match the predicate.
    ///
    /// # Panics
    ///
    /// Panics with an informative message if any of items does not match the predicate.
    pub fn all<P: Predicate<I::Item> + ?Sized>(self, predicate: &P) {
        let mut iter = self.iter();
        if let Some(item) = iter.find(|item| !predicate.eval(item)) {
            panic!("item does not match predicate {predicate}: {item:#?}");
        }
    }

    /// Checks that none of the items match the predicate.
    ///
    /// # Panics
    ///
    /// Panics with an informative message if any of items match the predicate.
    pub fn none<P: Predicate<I::Item> + ?Sized>(self, predicate: &P) {
        let mut iter = self.iter();
        if let Some(item) = iter.find(|item| predicate.eval(item)) {
            panic!("item matched predicate {predicate}: {item:#?}");
        }
    }
}

impl<T, I> Scanner<T, I>
where
    I: DoubleEndedIterator,
    I::Item: fmt::Debug,
{
    /// Finds the last item matching the predicate.
    ///
    /// # Panics
    ///
    /// Panics with an informative message if no items match the predicate.
    pub fn last<P: Predicate<I::Item> + ?Sized>(self, predicate: &P) -> I::Item {
        let mut iter = self.iter().rev();
        iter.find(|item| predicate.eval(item))
            .unwrap_or_else(|| panic!("no items have matched predicate {predicate}"))
    }
}