allow-report 0.1.1

Report and receipt rendering for cargo-allow source exception scans.
Documentation
use crate::contracts::PROPOSE_ARTIFACT;
use crate::json::{bool_json, option_json, push_json_fixed_artifact_preamble};
use crate::{CLAIM_BOUNDARY_TEXT, ProposeReport};
use allow_core::json_escape;

pub fn render_propose_human(report: ProposeReport<'_>) -> String {
    let mut out = String::new();
    out.push_str("cargo-allow propose summary\n");
    out.push_str(&format!(
        "inventory: {}/{} via {}{}\n",
        report.inventory.scope,
        report.inventory.scanner,
        report.inventory.source,
        propose_inventory_files_suffix(report.inventory)
    ));
    if let Some(root) = report.inventory.root {
        out.push_str(&format!("source_tree_root: {root}\n"));
    }
    if let Some(kind) = report.kind {
        out.push_str(&format!("kind filter: {kind}\n"));
    }
    out.push_str(&format!("findings scanned: {}\n", report.findings_scanned));
    out.push_str(&format!(
        "baseline_debt entries proposed: {}\n",
        report.baseline_debt_entries_proposed
    ));
    out.push_str(&format!(
        "unsafe baseline_debt entries proposed: {}\n",
        report.unsafe_baseline_debt_entries_proposed
    ));
    out.push_str("owner: unowned\n");
    out.push_str("classification: baseline_debt\n");
    out.push_str("reason: Generated by cargo-allow propose; requires human review.\n");
    out.push_str(&format!("expires: {}\n", report.expires));
    if let Some(output) = report.policy_output {
        out.push_str(&format!("output: {output}\n"));
    } else {
        out.push_str("output: stdout\n");
    }
    out.push_str(
        "claim boundary: proposal only; generated debt still requires human review and evidence.\n",
    );
    out.push_str(CLAIM_BOUNDARY_TEXT);
    out.push('\n');
    out
}

fn propose_inventory_files_suffix(inventory: crate::InventoryContext<'_>) -> String {
    inventory
        .files_scanned
        .map(|files| format!("; files scanned: {files}"))
        .unwrap_or_default()
}

pub fn render_propose_json(report: ProposeReport<'_>) -> String {
    let mut out = String::new();
    out.push_str("{\n");
    push_json_fixed_artifact_preamble(&mut out, PROPOSE_ARTIFACT, report.inventory);
    out.push_str("  \"options\": {\n");
    out.push_str(&format!("    \"kind\": {},\n", option_json(report.kind)));
    out.push_str(&format!(
        "    \"expires\": \"{}\",\n",
        json_escape(report.expires)
    ));
    out.push_str(&format!(
        "    \"policy_output\": {},\n",
        option_json(report.policy_output)
    ));
    out.push_str(&format!("    \"force\": {}\n", bool_json(report.force)));
    out.push_str("  },\n");
    out.push_str("  \"summary\": {\n");
    out.push_str(&format!(
        "    \"findings_scanned\": {},\n",
        report.findings_scanned
    ));
    out.push_str(&format!(
        "    \"baseline_debt_entries_proposed\": {},\n",
        report.baseline_debt_entries_proposed
    ));
    out.push_str(&format!(
        "    \"unsafe_baseline_debt_entries_proposed\": {}\n",
        report.unsafe_baseline_debt_entries_proposed
    ));
    out.push_str("  },\n");
    out.push_str("  \"generated_entry_defaults\": {\n");
    out.push_str("    \"owner\": \"unowned\",\n");
    out.push_str("    \"classification\": \"baseline_debt\",\n");
    out.push_str("    \"reason\": \"Generated by cargo-allow propose; requires human review.\",\n");
    out.push_str(&format!(
        "    \"expires\": \"{}\"\n",
        json_escape(report.expires)
    ));
    out.push_str("  }\n");
    out.push_str("}\n");
    out
}