licensebat_cli/
check.rs

1use crate::Cli;
2use futures::StreamExt;
3use licensebat_core::{licrc::LicRc, FileCollector, RetrievedDependency};
4use std::sync::Arc;
5
6const LICENSE_CACHE: &[u8] = std::include_bytes!("../license-cache.bin.zstd");
7
8#[derive(Debug, thiserror::Error)]
9enum CheckError {
10    #[error("Error reading dependency file: {0}")]
11    DependencyFile(#[from] std::io::Error),
12}
13
14/// Result of the dependency validation.
15pub struct RunResult {
16    /// The [`LicRc`] file.
17    pub licrc: LicRc,
18    /// The validated dependencies.
19    pub dependencies: Vec<RetrievedDependency>,
20}
21
22/// Checks the dependencies of a project.
23///
24/// This is the main entry point of the CLI.
25///
26/// # Errors
27///
28/// Errors can be caused by many causes, including:
29/// - Reading a dependency manifest file (package-lock.json, yarn.lock, etc.)
30/// - Reading the .licrc file
31pub async fn run(cli: Cli) -> anyhow::Result<RunResult> {
32    tracing::info!(
33        dependency_file = %cli.dependency_file,
34        "Licensebat running! Using {}", cli.dependency_file
35    );
36
37    // 0. spdx store & http client
38    let store = Arc::new(askalono::Store::from_cache(LICENSE_CACHE).ok());
39    let client = reqwest::Client::builder()
40        .no_proxy()
41        // .danger_accept_invalid_certs(true)
42        // .pool_idle_timeout(None)
43        .build()
44        .expect("Failed to build HTTP client");
45
46    // 1 .get information from .licrc file
47    tracing::debug!("Reading .licrc file");
48    let licrc = LicRc::from_relative_path(cli.licrc_file)?;
49
50    // 2. get content of the dependency file
51    tracing::debug!("Getting dependency file content");
52    let dep_file_content = get_dep_file_content(&cli.dependency_file).await?;
53
54    // 3. create collectors
55    tracing::debug!("Building collectors");
56    let npm_retriever = licensebat_js::retriever::Npm::new(client.clone());
57    let npm_collector = licensebat_js::collector::Npm::new(npm_retriever.clone());
58    let yarn_collector = licensebat_js::collector::Yarn::new(npm_retriever);
59    let rust_collector =
60        licensebat_rust::collector::Rust::with_docs_rs_retriever(client.clone(), store.clone());
61    let dart_collector = licensebat_dart::collector::Dart::with_hosted_retriever(client, store);
62
63    let file_collectors: Vec<Box<dyn FileCollector>> = vec![
64        Box::new(npm_collector),
65        Box::new(yarn_collector),
66        Box::new(rust_collector),
67        Box::new(dart_collector),
68    ];
69
70    // 4. get dependency stream
71    let mut stream = file_collectors
72        .iter()
73        .find(|c| cli.dependency_file.contains(&c.get_dependency_filename()))
74        .and_then(|c| c.get_dependencies(&dep_file_content, &licrc).ok())
75        .expect(
76            format!(
77                "No collector found for dependency file {}",
78                cli.dependency_file
79            )
80            .as_str(),
81        )
82        .buffer_unordered(licrc.behavior.retriever_buffer_size.unwrap_or(100));
83
84    // 5. validate the dependencies according to the .licrc config
85    tracing::debug!("Validating dependencies");
86    let mut validated_deps = vec![];
87
88    while let Some(mut dependency) = stream.next().await {
89        // do the validation here
90        licrc.validate(&mut dependency);
91        validated_deps.push(dependency);
92    }
93
94    tracing::info!("Done!");
95    Ok(RunResult {
96        licrc,
97        dependencies: validated_deps,
98    })
99}
100
101async fn get_dep_file_content(dependency_file: &str) -> Result<String, CheckError> {
102    async {
103        let dep_file_path = std::env::current_dir()?.join(dependency_file);
104        let dep_file_content = tokio::fs::read_to_string(dep_file_path).await?;
105        Ok(dep_file_content)
106    }
107    .await
108    .map_err(CheckError::DependencyFile)
109}