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
use crate::discover::DiscoveredAdvisory;
use crate::retrieve::{RetrievalContext, RetrievalError, RetrievedAdvisory, RetrievedVisitor};
use crate::validation::{ValidatedAdvisory, ValidatedVisitor, ValidationContext, ValidationError};
use async_trait::async_trait;
use reqwest::{Body, Method, StatusCode};
use url::Url;
use walker_common::sender::{self, HttpSender};

/// Stores all data so that it can be used as a [`crate::source::Source`] later.
pub struct SendVisitor {
    /// The target endpoint
    pub url: Url,

    /// The HTTP client to use
    pub sender: HttpSender,
}

#[derive(Debug, thiserror::Error)]
pub enum SendError {
    #[error(transparent)]
    Sender(#[from] sender::Error),
    #[error(transparent)]
    Request(#[from] reqwest::Error),
    #[error("server error: {0}")]
    Server(StatusCode),
    #[error("unexpected status: {0}")]
    UnexpectedStatus(StatusCode),
}

#[derive(Debug, thiserror::Error)]
pub enum SendRetrievedError {
    #[error(transparent)]
    Store(#[from] SendError),
    #[error(transparent)]
    Retrieval(#[from] RetrievalError),
}

#[derive(Debug, thiserror::Error)]
pub enum SendValidatedError {
    #[error(transparent)]
    Store(#[from] SendError),
    #[error(transparent)]
    Validation(#[from] ValidationError),
}

#[async_trait(?Send)]
impl RetrievedVisitor for SendVisitor {
    type Error = SendRetrievedError;
    type Context = ();

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

    async fn visit_advisory(
        &self,
        _context: &Self::Context,
        result: Result<RetrievedAdvisory, RetrievalError>,
    ) -> Result<(), Self::Error> {
        self.send(result?).await?;
        Ok(())
    }
}

#[async_trait(?Send)]
impl ValidatedVisitor for SendVisitor {
    type Error = SendValidatedError;
    type Context = ();

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

    async fn visit_advisory(
        &self,
        _context: &Self::Context,
        result: Result<ValidatedAdvisory, ValidationError>,
    ) -> Result<(), Self::Error> {
        self.send(result?.retrieved).await?;
        Ok(())
    }
}

impl SendVisitor {
    async fn send(&self, advisory: RetrievedAdvisory) -> Result<(), SendError> {
        log::debug!(
            "Sending: {} (modified: {:?})",
            advisory.url,
            advisory.metadata.last_modification
        );

        let RetrievedAdvisory {
            data,
            discovered: DiscoveredAdvisory { url, .. },
            ..
        } = advisory;

        let response = self
            .sender
            .request(Method::POST, self.url.clone())
            .await?
            .body(Body::from(data))
            .send()
            .await?;

        let status = response.status();

        if status.is_success() {
            log::debug!("Uploaded {} -> {}", url, response.status());
            Ok(())
        } else if status.is_client_error() {
            log::warn!("Failed to upload, payload rejected {url} -> {status}",);
            Ok(())
        } else if status.is_server_error() {
            log::warn!("Failed to upload, server error {url} -> {status}",);
            Err(SendError::Server(status))
        } else {
            Err(SendError::UnexpectedStatus(status))
        }
    }
}