sbom_walker/visitors/skip/
mod.rs1use crate::{
2 discover::{DiscoveredContext, DiscoveredSbom, DiscoveredVisitor},
3 source::Source,
4 validation::{ValidatedSbom, ValidatedVisitor, ValidationContext},
5};
6use std::{
7 fmt::{Debug, Display},
8 path::PathBuf,
9 time::SystemTime,
10};
11use tokio::fs;
12use walker_common::{utils::url::Urlify, validate::ValidationError};
13
14#[derive(Debug, thiserror::Error)]
15pub enum Error<VE: Display + Debug> {
16 #[error("{0}")]
17 Visitor(VE),
18 #[error("I/O error: {0}")]
19 Io(#[from] std::io::Error),
20 #[error("Unable to get name from URL")]
21 Name,
22}
23
24pub struct SkipExistingVisitor<V: DiscoveredVisitor> {
26 pub visitor: V,
27 pub output: PathBuf,
28 pub since: Option<SystemTime>,
32}
33
34impl<V: DiscoveredVisitor> DiscoveredVisitor for SkipExistingVisitor<V> {
35 type Error = Error<V::Error>;
36 type Context = V::Context;
37
38 async fn visit_context(
39 &self,
40 context: &DiscoveredContext<'_>,
41 ) -> Result<Self::Context, Self::Error> {
42 self.visitor
43 .visit_context(context)
44 .await
45 .map_err(Error::Visitor)
46 }
47
48 async fn visit_sbom(
49 &self,
50 context: &Self::Context,
51 sbom: DiscoveredSbom,
52 ) -> Result<(), Self::Error> {
53 let name = PathBuf::from(sbom.url.path());
54 let name = name.file_name().ok_or(Error::Name)?;
55
56 let path = self.output.join(name);
57
58 if fs::try_exists(&path).await? {
59 let file_modified = match self.since {
61 Some(since) => since,
62 None => fs::metadata(&path).await?.modified()?,
63 };
64
65 log::debug!(
66 "Advisory modified: {}, file ({}) modified: {} ({:?})",
67 humantime::Timestamp::from(sbom.modified),
68 name.to_string_lossy(),
69 humantime::Timestamp::from(file_modified),
70 self.since.map(humantime::Timestamp::from)
71 );
72
73 if file_modified >= sbom.modified {
74 return Ok(());
76 }
77 }
78
79 self.visitor
80 .visit_sbom(context, sbom)
81 .await
82 .map_err(Error::Visitor)
83 }
84}
85
86pub struct SkipFailedVisitor<V> {
88 pub visitor: V,
89 pub skip_failures: bool,
90}
91
92impl<V> SkipFailedVisitor<V> {
93 pub fn new(visitor: V) -> Self {
94 Self {
95 visitor,
96 skip_failures: true,
97 }
98 }
99}
100
101impl<V: ValidatedVisitor<S>, S: Source> ValidatedVisitor<S> for SkipFailedVisitor<V> {
102 type Error = V::Error;
103 type Context = V::Context;
104
105 async fn visit_context(
106 &self,
107 context: &ValidationContext<'_>,
108 ) -> Result<Self::Context, Self::Error> {
109 self.visitor.visit_context(context).await
110 }
111
112 async fn visit_sbom(
113 &self,
114 context: &Self::Context,
115 result: Result<ValidatedSbom, ValidationError<S>>,
116 ) -> Result<(), Self::Error> {
117 match (self.skip_failures, result) {
118 (_, Ok(result)) => self.visitor.visit_sbom(context, Ok(result)).await,
119 (false, Err(err)) => self.visitor.visit_sbom(context, Err(err)).await,
120 (true, Err(err)) => {
121 log::warn!("Skipping failed SBOM {}: {err}", err.url());
122 Ok(())
123 }
124 }
125 }
126}