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
//! Discovering

use crate::model::metadata::ProviderMetadata;
use std::fmt::Debug;
use std::future::Future;
use std::sync::Arc;
use std::time::SystemTime;
use url::Url;
use walker_common::utils::url::Urlify;

/// Discovery configuration
pub struct DiscoverConfig {
    /// The source to locate the provider metadata.
    ///
    /// This can be either a full path to a provider-metadata.json, or a base domain used by the
    /// CSAF metadata discovery process.
    pub source: String,

    /// Only report documents which have changed since the provided date. If a document has no
    /// change information, or this field is [`None`], it will always be reported.
    pub since: Option<SystemTime>,
}

impl DiscoverConfig {
    pub fn with_since(mut self, since: impl Into<Option<SystemTime>>) -> Self {
        self.since = since.into();
        self
    }
}

impl From<&str> for DiscoverConfig {
    fn from(value: &str) -> Self {
        Self {
            since: None,
            source: value.to_string(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DistributionContext {
    Directory(Url),
    Feed(Url),
}

impl DistributionContext {
    /// Get the URL of the distribution
    pub fn url(&self) -> &Url {
        match self {
            Self::Directory(url) => url,
            Self::Feed(url) => url,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiscoveredAdvisory {
    /// A reference to the distribution and rolie information
    pub context: Arc<DistributionContext>,
    /// The URL of the advisory
    pub url: Url,
    /// The "last changed" date from the change information
    pub modified: SystemTime,
}

/// Get a document as [`DiscoveredAdvisory`]
pub trait AsDiscovered: Debug {
    fn as_discovered(&self) -> &DiscoveredAdvisory;
}

impl AsDiscovered for DiscoveredAdvisory {
    fn as_discovered(&self) -> &DiscoveredAdvisory {
        self
    }
}

impl Urlify for DiscoveredAdvisory {
    fn url(&self) -> &Url {
        &self.url
    }

    fn relative_base_and_url(&self) -> Option<(&Url, String)> {
        self.context
            .url()
            .make_relative(&self.url)
            .map(|relative| (self.context.url(), relative))
    }
}

#[derive(Debug)]
pub struct DiscoveredContext<'c> {
    pub metadata: &'c ProviderMetadata,
}

/// Visiting discovered advisories
pub trait DiscoveredVisitor {
    type Error: std::fmt::Display + Debug;
    type Context;

    fn visit_context(
        &self,
        context: &DiscoveredContext,
    ) -> impl Future<Output = Result<Self::Context, Self::Error>>;

    fn visit_advisory(
        &self,
        context: &Self::Context,
        advisory: DiscoveredAdvisory,
    ) -> impl Future<Output = Result<(), Self::Error>>;
}

impl<F, E, Fut> DiscoveredVisitor for F
where
    F: Fn(DiscoveredAdvisory) -> Fut,
    Fut: Future<Output = Result<(), E>>,
    E: std::fmt::Display + Debug,
{
    type Error = E;
    type Context = ();

    async fn visit_context(
        &self,
        _context: &DiscoveredContext<'_>,
    ) -> Result<Self::Context, Self::Error> {
        Ok(())
    }

    async fn visit_advisory(
        &self,
        _ctx: &Self::Context,
        advisory: DiscoveredAdvisory,
    ) -> Result<(), Self::Error> {
        self(advisory).await
    }
}