cargo-allow 0.1.3

Source-tree exception ledger and policy scanner for Rust repositories.
use allow_core::{AllowConfig, AllowEntry, CargoAllowError, CargoAllowResult, normalize_path};
use allow_policy::{
    EvidenceReferenceCategory, EvidenceReferenceDiagnostic, EvidenceReferenceSource,
    EvidenceReferenceStatus, evidence_reference_diagnostics, policy_reference_diagnostics,
};
use std::collections::BTreeSet;
use std::fs;
use std::path::Path;

pub(crate) const DEFAULT_SOURCE_TREE_INVENTORY_EVIDENCE_MESSAGE: &str =
    "local evidence file exists but is not in the default source-tree inventory";

pub(crate) fn current_evidence_source_tree_files(
    root: &Path,
    include_untracked: bool,
) -> Option<BTreeSet<String>> {
    if include_untracked {
        return None;
    }
    let Ok(files) = allow_inventory::git_ls_files(root) else {
        return None;
    };
    Some(
        files
            .into_iter()
            .filter(|path| {
                fs::symlink_metadata(root.join(path))
                    .map(|metadata| metadata.file_type().is_file())
                    .unwrap_or(false)
            })
            .map(normalize_path)
            .collect(),
    )
}

pub(crate) fn evidence_reference_diagnostics_for_source_tree(
    root: &Path,
    entry: &AllowEntry,
    source_tree_files: Option<&BTreeSet<String>>,
) -> Vec<EvidenceReferenceDiagnostic> {
    let mut diagnostics = evidence_reference_diagnostics(root, entry);
    for diagnostic in &mut diagnostics {
        apply_source_tree_inventory_to_diagnostic(diagnostic, source_tree_files);
    }
    diagnostics
}

pub(crate) fn validate_evidence_references_for_source_tree(
    root: &Path,
    cfg: &AllowConfig,
    source_tree_files: Option<&BTreeSet<String>>,
) -> CargoAllowResult<()> {
    for entry in &cfg.allow {
        for reference in
            policy_reference_diagnostics_for_source_tree(root, entry, source_tree_files)
        {
            if reference.diagnostic.status.is_broken_local_link() {
                return Err(CargoAllowError::new(format!(
                    "{} {} `{}`: {}",
                    entry.id,
                    reference.source.label(),
                    reference.diagnostic.raw,
                    reference.source.message(&reference.diagnostic.message)
                )));
            }
        }
    }
    Ok(())
}

pub(crate) fn policy_reference_diagnostics_for_source_tree(
    root: &Path,
    entry: &AllowEntry,
    source_tree_files: Option<&BTreeSet<String>>,
) -> Vec<PolicyReferenceDiagnostic> {
    policy_reference_diagnostics(root, entry)
        .into_iter()
        .map(|mut reference| {
            apply_source_tree_inventory_to_diagnostic(&mut reference.diagnostic, source_tree_files);
            reference
        })
        .collect()
}

pub(crate) type PolicyReferenceDiagnostic = allow_policy::PolicyReferenceDiagnostic;
pub(crate) type ReferenceSource = EvidenceReferenceSource;

fn apply_source_tree_inventory_to_diagnostic(
    diagnostic: &mut EvidenceReferenceDiagnostic,
    source_tree_files: Option<&BTreeSet<String>>,
) {
    let Some(source_tree_files) = source_tree_files else {
        return;
    };
    if diagnostic.status != EvidenceReferenceStatus::LocalFilePresent {
        return;
    }
    let Some(target) = diagnostic.target.as_ref() else {
        return;
    };
    if source_tree_files.contains(&normalize_path(target)) {
        return;
    }
    diagnostic.status = EvidenceReferenceStatus::LocalFileMissing;
    diagnostic.category = EvidenceReferenceCategory::Missing;
    diagnostic.message = DEFAULT_SOURCE_TREE_INVENTORY_EVIDENCE_MESSAGE.to_string();
}