logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use crate::Cli;
use futures::StreamExt;
use licensebat_core::{licrc::LicRc, FileCollector, RetrievedDependency};
use std::sync::Arc;

const LICENSE_CACHE: &[u8] = std::include_bytes!("../license-cache.bin.zstd");

#[derive(Debug, thiserror::Error)]
enum CheckError {
    #[error("Error reading dependency file: {0}")]
    DependencyFile(#[from] std::io::Error),
}

/// Result of the dependency validation.
pub struct RunResult {
    /// The [`LicRc`] file.
    pub licrc: LicRc,
    /// The validated dependencies.
    pub dependencies: Vec<RetrievedDependency>,
}

/// Checks the dependencies of a project.
///
/// This is the main entry point of the CLI.
///
/// # Errors
///
/// Errors can be caused by many causes, including:
/// - Reading a dependency manifest file (package-lock.json, yarn.lock, etc.)
/// - Reading the .licrc file
pub async fn run(cli: Cli) -> anyhow::Result<RunResult> {
    tracing::info!(
        dependency_file = %cli.dependency_file,
        "Licensebat running! Using {}", cli.dependency_file
    );

    // 0. spdx store & http client
    let store = Arc::new(askalono::Store::from_cache(LICENSE_CACHE).ok());
    let client = reqwest::Client::new();

    // 1 .get information from .licrc file
    tracing::debug!("Reading .licrc file");
    let licrc = LicRc::from_relative_path(cli.licrc_file)?;

    // 2. get content of the dependency file
    tracing::debug!("Getting dependency file content");
    let dep_file_content = get_dep_file_content(&cli.dependency_file).await?;

    // 3. create collectors
    tracing::debug!("Building collectors");
    let npm_retriever = licensebat_js::retriever::Npm::new(client.clone());
    let npm_collector = licensebat_js::collector::Npm::new(npm_retriever.clone());
    let yarn_collector = licensebat_js::collector::Yarn::new(npm_retriever);
    let rust_collector =
        licensebat_rust::collector::Rust::with_docs_rs_retriever(client.clone(), store.clone());
    let dart_collector = licensebat_dart::collector::Dart::with_hosted_retriever(client, store);

    let file_collectors: Vec<Box<dyn FileCollector>> = vec![
        Box::new(npm_collector),
        Box::new(yarn_collector),
        Box::new(rust_collector),
        Box::new(dart_collector),
    ];

    // 4. get dependency stream
    let mut stream = file_collectors
        .iter()
        .find(|c| cli.dependency_file.contains(&c.get_dependency_filename()))
        .and_then(|c| c.get_dependencies(&dep_file_content).ok())
        .expect("No collector found for dependency file");

    // 5. validate the dependencies according to the .licrc config
    tracing::debug!("Validating dependencies");
    let mut validated_deps = vec![];
    while let Some(mut dependency) = stream.next().await {
        // do the validation here
        licrc.validate(&mut dependency);
        validated_deps.push(dependency);
    }

    tracing::info!("Done!");
    Ok(RunResult {
        licrc,
        dependencies: validated_deps,
    })
}

async fn get_dep_file_content(dependency_file: &str) -> Result<String, CheckError> {
    async {
        let dep_file_path = std::env::current_dir()?.join(dependency_file);
        let dep_file_content = tokio::fs::read_to_string(dep_file_path).await?;
        Ok(dep_file_content)
    }
    .await
    .map_err(CheckError::DependencyFile)
}