allow-report 0.1.7

Report and receipt rendering for cargo-allow source exception scans.
Documentation
use crate::contracts::DOCTOR_ARTIFACT;
use crate::evidence_repair::{
    BROKEN_EVIDENCE_LINK_COMMAND, EvidenceRepairQueue, WEAK_EVIDENCE_REFERENCE_COMMAND,
    evidence_repair_queues_from_counts, push_evidence_repair_queue_json_fields,
};
use crate::json::{bool_json, option_json, push_json_fixed_artifact_preamble};
use crate::{CLAIM_BOUNDARY_TEXT, DoctorReport, InventoryContext};
use allow_core::json_escape;

pub fn render_doctor_human(facts: DoctorReport<'_>) -> String {
    let mut out = String::new();
    out.push_str(&format!("source tree root: {}\n", facts.source_tree_root));
    out.push_str(&format!("root discovery: {}\n", facts.root_discovery));
    match facts.config_path {
        Some(path) => {
            out.push_str(&format!("config: {path}\n"));
            if let Some(schema_version) = facts.config_schema_version {
                out.push_str(&format!("policy schema version: {schema_version}\n"));
            }
            if let Some(policy) = facts.config_policy {
                out.push_str(&format!("policy: {policy}\n"));
            }
            if let Some(owner) = facts.config_owner {
                out.push_str(&format!("policy owner: {owner}\n"));
            }
            if let Some(status) = facts.config_status {
                out.push_str(&format!("policy status: {status}\n"));
            }
            out.push_str(&format!(
                "config status: {}\n",
                config_status_text(facts.config_valid, facts.config_diagnostic)
            ));
            if let Some(count) = facts.broken_evidence_links {
                out.push_str(&format!("broken evidence links: {count}\n"));
                if count > 0 {
                    out.push_str(&format!(
                        "broken evidence worklist: {BROKEN_EVIDENCE_LINK_COMMAND}\n"
                    ));
                }
            }
            if let Some(count) = facts.weak_evidence_references {
                out.push_str(&format!("weak evidence/link references: {count}\n"));
                if count > 0 {
                    out.push_str(&format!(
                        "weak evidence worklist: {WEAK_EVIDENCE_REFERENCE_COMMAND}\n"
                    ));
                }
            }
        }
        None => out.push_str(&format!(
            "config: not found; run `{}`\n",
            suggested_init_command(facts.source_tree_root)
        )),
    }
    out.push_str(&format!(
        "inventory: source_tree/source_syntax via {}; files scanned: {}\n",
        facts.inventory_source, facts.files_scanned
    ));
    out.push_str(CLAIM_BOUNDARY_TEXT);
    out
}

pub fn render_doctor_json(facts: DoctorReport<'_>) -> String {
    let mut out = String::new();
    out.push_str("{\n");
    push_json_fixed_artifact_preamble(
        &mut out,
        DOCTOR_ARTIFACT,
        InventoryContext::source_syntax(
            facts.inventory_source,
            Some(facts.source_tree_root),
            Some(facts.files_scanned),
        ),
    );
    out.push_str("  \"root\": {\n");
    out.push_str(&format!(
        "    \"path\": \"{}\",\n",
        json_escape(facts.source_tree_root)
    ));
    out.push_str(&format!(
        "    \"discovery\": \"{}\"\n",
        json_escape(facts.root_discovery)
    ));
    out.push_str("  },\n");
    out.push_str("  \"config\": {\n");
    out.push_str(&format!(
        "    \"found\": {},\n",
        bool_json(facts.config_path.is_some())
    ));
    out.push_str(&format!(
        "    \"path\": {},\n",
        option_json(facts.config_path)
    ));
    out.push_str(&format!(
        "    \"schema_version\": {},\n",
        option_json(facts.config_schema_version)
    ));
    out.push_str(&format!(
        "    \"policy\": {},\n",
        option_json(facts.config_policy)
    ));
    out.push_str(&format!(
        "    \"owner\": {},\n",
        option_json(facts.config_owner)
    ));
    out.push_str(&format!(
        "    \"status\": {},\n",
        option_json(facts.config_status)
    ));
    out.push_str(&format!(
        "    \"valid\": {},\n",
        option_bool_json(facts.config_valid)
    ));
    out.push_str(&format!(
        "    \"diagnostic\": {}",
        option_json(facts.config_diagnostic)
    ));
    if facts.config_path.is_none() {
        out.push_str(&format!(
            ",\n    \"suggested_init_command\": \"{}\"",
            json_escape(&suggested_init_command(facts.source_tree_root))
        ));
    }
    if let Some(count) = facts.broken_evidence_links {
        out.push_str(&format!(",\n    \"broken_evidence_links\": {count}"));
    }
    if let Some(count) = facts.weak_evidence_references {
        out.push_str(&format!(",\n    \"weak_evidence_references\": {count}"));
    }
    append_doctor_evidence_repair_queues_json(facts, &mut out);
    out.push('\n');
    out.push_str("  }\n");
    out.push_str("}\n");
    out
}

fn append_doctor_evidence_repair_queues_json(facts: DoctorReport<'_>, out: &mut String) {
    let queues = doctor_evidence_repair_queues(facts);
    if queues.is_empty() {
        return;
    }

    out.push_str(",\n    \"evidence_repair_queues\": [\n");
    for (index, queue) in queues.iter().enumerate() {
        if index > 0 {
            out.push_str(",\n");
        }
        out.push_str("      {\n");
        push_evidence_repair_queue_json_fields(out, queue, "        ");
        out.push_str("      }");
    }
    out.push_str("\n    ]");
}

fn doctor_evidence_repair_queues(facts: DoctorReport<'_>) -> Vec<EvidenceRepairQueue> {
    evidence_repair_queues_from_counts(
        facts.broken_evidence_links.unwrap_or(0),
        0,
        facts.weak_evidence_references.unwrap_or(0),
    )
}

fn config_status_text(valid: Option<bool>, diagnostic: Option<&str>) -> String {
    match (valid, diagnostic) {
        (Some(true), _) => "valid".to_string(),
        (Some(false), Some(diagnostic)) => format!("invalid: {diagnostic}"),
        (Some(false), None) => "invalid".to_string(),
        (None, _) => "not checked".to_string(),
    }
}

fn option_bool_json(value: Option<bool>) -> &'static str {
    match value {
        Some(true) => "true",
        Some(false) => "false",
        None => "null",
    }
}

fn suggested_init_command(source_tree_root: &str) -> String {
    format!("cargo-allow init --root \"{source_tree_root}\"")
}