Skip to main content

walker_extras/visitors/send/
sbom.rs

1use super::*;
2use crate::sbom::{
3    discover::DiscoveredSbom,
4    retrieve::{RetrievalContext, RetrievedSbom, RetrievedVisitor},
5    validation::{ValidatedSbom, ValidatedVisitor, ValidationContext},
6};
7use reqwest::header;
8use sbom_walker::source::Source;
9use walker_common::{retrieve::RetrievalError, validate::ValidationError};
10
11#[derive(Debug, thiserror::Error)]
12pub enum SendRetrievedSbomError<S: Source> {
13    #[error(transparent)]
14    Store(#[from] SendError),
15    #[error(transparent)]
16    Retrieval(#[from] RetrievalError<DiscoveredSbom, S>),
17}
18
19impl<S: Source> RetrievedVisitor<S> for SendVisitor {
20    type Error = SendRetrievedSbomError<S>;
21    type Context = ();
22
23    async fn visit_context(&self, _: &RetrievalContext<'_>) -> Result<Self::Context, Self::Error> {
24        Ok(())
25    }
26
27    async fn visit_sbom(
28        &self,
29        _context: &Self::Context,
30        result: Result<RetrievedSbom, RetrievalError<DiscoveredSbom, S>>,
31    ) -> Result<(), Self::Error> {
32        self.send_sbom(result?).await?;
33        Ok(())
34    }
35}
36
37#[derive(Debug, thiserror::Error)]
38pub enum SendValidatedSbomError<S: Source> {
39    #[error(transparent)]
40    Store(#[from] SendError),
41    #[error(transparent)]
42    Validation(#[from] ValidationError<S>),
43}
44
45impl<S: Source> ValidatedVisitor<S> for SendVisitor {
46    type Error = SendValidatedSbomError<S>;
47    type Context = ();
48
49    async fn visit_context(&self, _: &ValidationContext<'_>) -> Result<Self::Context, Self::Error> {
50        Ok(())
51    }
52
53    async fn visit_sbom(
54        &self,
55        _context: &Self::Context,
56        result: Result<ValidatedSbom, ValidationError<S>>,
57    ) -> Result<(), Self::Error> {
58        self.send_sbom(result?.retrieved).await?;
59        Ok(())
60    }
61}
62
63impl SendVisitor {
64    async fn send_sbom(&self, sbom: RetrievedSbom) -> Result<(), SendError> {
65        log::debug!(
66            "Sending: {} (modified: {:?})",
67            sbom.url,
68            sbom.metadata.last_modification
69        );
70
71        let RetrievedSbom {
72            data,
73            discovered: DiscoveredSbom { url, .. },
74            ..
75        } = sbom;
76
77        let name = url
78            .path_segments()
79            .and_then(|mut p| p.next_back())
80            .unwrap_or_else(|| url.path());
81
82        if !(name.ends_with(".json") || name.ends_with(".json.bz2")) {
83            log::warn!("Skipping unknown file: {name}");
84            return Ok(());
85        }
86
87        let bzip2 = name.ends_with(".bz2");
88
89        self.send(url.as_str(), data, |mut request| {
90            request = request
91                .query(&[("id", name)])
92                .header(header::CONTENT_TYPE, "application/json");
93            if bzip2 {
94                request.header(header::CONTENT_ENCODING, "bzip2")
95            } else {
96                request
97            }
98        })
99        .await
100    }
101}