Skip to main content

sbom_walker/source/
mod.rs

1//! Sources
2
3mod dispatch;
4mod file;
5mod http;
6
7pub use self::http::*;
8pub use dispatch::*;
9pub use file::*;
10
11use crate::{
12    discover::{DiscoverConfig, DiscoveredSbom},
13    model::metadata::SourceMetadata,
14    retrieve::RetrievedSbom,
15};
16use anyhow::bail;
17use fluent_uri::UriRef;
18use std::{fmt::Debug, future::Future};
19use url::Url;
20use walker_common::fetcher::{Fetcher, FetcherOptions};
21
22/// A source of SBOM documents
23pub trait Source: walker_common::source::Source + Clone + Debug {
24    fn load_metadata(&self) -> impl Future<Output = Result<SourceMetadata, Self::Error>>;
25    fn load_index(&self) -> impl Future<Output = Result<Vec<DiscoveredSbom>, Self::Error>>;
26    fn load_sbom(
27        &self,
28        sbom: DiscoveredSbom,
29    ) -> impl Future<Output = Result<RetrievedSbom, Self::Error>>;
30}
31
32pub async fn new_source(
33    discover: impl Into<DiscoverConfig>,
34    fetcher: impl Into<FetcherOptions>,
35) -> anyhow::Result<DispatchSource> {
36    let discover = discover.into();
37    let source = discover.source;
38
39    match UriRef::parse(source.as_str()) {
40        Ok(uri) => match uri.scheme().map(|s| s.as_str()) {
41            Some("file") => {
42                let source = uri.path().as_str();
43                log::debug!("Creating file source: {source}");
44                Ok(FileSource::new(source, FileOptions::new().since(discover.since))?.into())
45            }
46            Some(_scheme) => {
47                log::debug!("Creating HTTP source: {source}");
48                let fetcher = Fetcher::new(fetcher.into()).await?;
49                Ok(HttpSource::new(
50                    Url::parse(&source)?,
51                    fetcher,
52                    HttpOptions::new().since(discover.since).keys(discover.keys),
53                )
54                .into())
55            }
56            None => {
57                bail!(
58                    "Failed to parse '{source}' as URL. For SBOMs there is no domain-based lookup"
59                );
60            }
61        },
62        Err(err) => {
63            bail!(
64                "Failed to parse '{source}' as URL. For SBOMs there is no domain-based lookup: {err}"
65            );
66        }
67    }
68}
69
70#[cfg(test)]
71mod test {
72    use crate::discover::DiscoverConfig;
73    use crate::source::new_source;
74    use walker_common::fetcher::FetcherOptions;
75
76    #[tokio::test]
77    pub async fn test_file_source() {
78        let result = new_source(
79            DiscoverConfig {
80                source: "file:/".to_string(),
81                since: None,
82                keys: vec![],
83            },
84            FetcherOptions::default(),
85        )
86        .await;
87
88        assert!(result.is_ok());
89    }
90
91    #[tokio::test]
92    pub async fn test_http_source() {
93        let result = new_source(
94            DiscoverConfig {
95                source: "https://foo.bar/baz".to_string(),
96                since: None,
97                keys: vec![],
98            },
99            FetcherOptions::default(),
100        )
101        .await;
102
103        assert!(result.is_ok());
104    }
105
106    #[tokio::test]
107    pub async fn test_invalid_source() {
108        let result = new_source(
109            DiscoverConfig {
110                source: "/var/files".to_string(),
111                since: None,
112                keys: vec![],
113            },
114            FetcherOptions::default(),
115        )
116        .await;
117
118        assert!(result.is_err());
119    }
120}