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
use crate::discover::{DiscoveredAdvisory, DiscoveredContext, DiscoveredVisitor};
use std::collections::HashSet;

/// A visitor, skipping advisories for existing files.
pub struct FilteringVisitor<V: DiscoveredVisitor> {
    pub visitor: V,

    pub config: FilterConfig,
}

#[non_exhaustive]
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct FilterConfig {
    /// A set of distributions to ignore
    ///
    /// **NOTE:** The distributions will still be discovered, as this is a post-discovery visitor. If you want to
    /// even skip discovering the source, use [`crate::walker::Walker::with_distribution_filter`].
    pub ignored_distributions: HashSet<String>,
    pub ignored_prefixes: Vec<String>,
    pub only_prefixes: Vec<String>,
}

impl FilterConfig {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn ignored_distributions<I>(mut self, ignored_distributions: I) -> Self
    where
        I: IntoIterator<Item = String>,
    {
        self.ignored_distributions = HashSet::from_iter(ignored_distributions);
        self
    }

    pub fn add_ignored_distribution(mut self, ignored_distribution: impl Into<String>) -> Self {
        self.ignored_distributions
            .insert(ignored_distribution.into());
        self
    }

    pub fn extend_ignored_distributions<I>(mut self, ignored_distributions: I) -> Self
    where
        I: IntoIterator<Item = String>,
    {
        self.ignored_distributions.extend(ignored_distributions);
        self
    }

    pub fn ignored_prefixes<I>(mut self, ignored_prefixes: I) -> Self
    where
        I: IntoIterator<Item = String>,
    {
        self.ignored_prefixes = Vec::from_iter(ignored_prefixes);
        self
    }

    pub fn add_ignored_prefix(mut self, ignored_prefix: impl Into<String>) -> Self {
        self.ignored_prefixes.push(ignored_prefix.into());
        self
    }

    pub fn extend_ignored_prefixes<I>(mut self, ignored_prefixes: I) -> Self
    where
        I: IntoIterator<Item = String>,
    {
        self.ignored_prefixes.extend(ignored_prefixes);
        self
    }

    pub fn only_prefixes<I>(mut self, only_prefixes: I) -> Self
    where
        I: IntoIterator<Item = String>,
    {
        self.only_prefixes = Vec::from_iter(only_prefixes);
        self
    }

    pub fn add_only_prefix(mut self, only_prefix: impl Into<String>) -> Self {
        self.only_prefixes.push(only_prefix.into());
        self
    }

    pub fn extend_only_prefixes<I>(mut self, only_prefixes: I) -> Self
    where
        I: IntoIterator<Item = String>,
    {
        self.only_prefixes.extend(only_prefixes);
        self
    }
}

impl<V: DiscoveredVisitor> DiscoveredVisitor for FilteringVisitor<V> {
    type Error = V::Error;
    type Context = V::Context;

    async fn visit_context(
        &self,
        discovered: &DiscoveredContext<'_>,
    ) -> Result<Self::Context, Self::Error> {
        self.visitor.visit_context(discovered).await
    }

    async fn visit_advisory(
        &self,
        context: &Self::Context,
        advisory: DiscoveredAdvisory,
    ) -> Result<(), Self::Error> {
        // ignore distributions

        if self
            .config
            .ignored_distributions
            .contains(advisory.context.url().as_str())
        {
            return Ok(());
        };
        // eval name

        let name = advisory
            .url
            .path_segments()
            .and_then(|seg| seg.last())
            .unwrap_or(advisory.url.path());

        // "ignore" prefix

        for n in &self.config.ignored_prefixes {
            if name.starts_with(n.as_str()) {
                return Ok(());
            }
        }

        // "only" prefix

        for n in &self.config.only_prefixes {
            if !name.starts_with(n.as_str()) {
                return Ok(());
            }
        }

        // ok to proceed

        self.visitor.visit_advisory(context, advisory).await
    }
}