walker_extras/visitors/send/
sbom.rs1use 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}