csaf_walker/visitors/skip/
mod.rs1use crate::discover::{DiscoveredAdvisory, DiscoveredContext, DiscoveredVisitor};
2use crate::model::store::distribution_base;
3use crate::source::Source;
4use crate::validation::{ValidatedAdvisory, ValidatedVisitor, ValidationContext, ValidationError};
5use std::fmt::{Debug, Display};
6use std::path::PathBuf;
7use std::time::SystemTime;
8use tokio::fs;
9use walker_common::utils::url::Urlify;
10
11#[derive(Debug, thiserror::Error)]
12pub enum Error<VE: Display + Debug> {
13 #[error("{0}")]
14 Visitor(VE),
15 #[error("I/O error: {0}")]
16 Io(#[from] std::io::Error),
17 #[error("Unable to get name from URL")]
18 Name,
19}
20
21pub struct SkipExistingVisitor<V: DiscoveredVisitor> {
23 pub visitor: V,
24 pub output: PathBuf,
25 pub since: Option<SystemTime>,
29}
30
31impl<V: DiscoveredVisitor> DiscoveredVisitor for SkipExistingVisitor<V> {
32 type Error = Error<V::Error>;
33 type Context = V::Context;
34
35 async fn visit_context(
36 &self,
37 context: &DiscoveredContext<'_>,
38 ) -> Result<Self::Context, Self::Error> {
39 self.visitor
40 .visit_context(context)
41 .await
42 .map_err(Error::Visitor)
43 }
44
45 async fn visit_advisory(
46 &self,
47 context: &Self::Context,
48 advisory: DiscoveredAdvisory,
49 ) -> Result<(), Self::Error> {
50 let name = match advisory.context.url().clone().make_relative(&advisory.url) {
51 Some(name) => name,
52 None => return Err(Error::Name),
53 };
54 let path = distribution_base(&self.output, advisory.context.url().as_str()).join(&name);
55
56 if fs::try_exists(&path).await? {
57 let file_modified = match self.since {
59 Some(since) => since,
60 None => fs::metadata(&path).await?.modified()?,
61 };
62
63 log::debug!(
64 "Advisory modified: {}, file ({}) modified: {} ({:?})",
65 humantime::Timestamp::from(advisory.modified),
66 name,
67 humantime::Timestamp::from(file_modified),
68 self.since.map(humantime::Timestamp::from)
69 );
70
71 if file_modified >= advisory.modified {
72 return Ok(());
74 }
75 } else {
76 log::debug!("File did not exist: {}", path.display());
77 }
78
79 self.visitor
80 .visit_advisory(context, advisory)
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_advisory(
113 &self,
114 context: &Self::Context,
115 result: Result<ValidatedAdvisory, ValidationError<S>>,
116 ) -> Result<(), Self::Error> {
117 match (self.skip_failures, result) {
118 (_, Ok(result)) => self.visitor.visit_advisory(context, Ok(result)).await,
119 (false, Err(err)) => self.visitor.visit_advisory(context, Err(err)).await,
120 (true, Err(err)) => {
121 log::warn!("Skipping failed advisory ({}): {err}", err.url());
122 Ok(())
123 }
124 }
125 }
126}