allow-report 0.1.9

Report and receipt rendering for cargo-allow source exception scans.
Documentation
use allow_core::json_escape;

use crate::DiffReport;
use crate::diff_posture::{diff_evidence_delta_summary, diff_structural_delta_summary};
use crate::explain_json::structural_identity_json;
use crate::json::{json_string_array, option_json};
use crate::{REPORT_COMMAND_DIFF, REPORT_SCHEMA_ID};

pub fn render_diff_json_with_posture(report_json: &str, report: DiffReport<'_>) -> Option<String> {
    if !is_diff_report_artifact(report_json) {
        return None;
    }
    let diff_json = render_diff_posture_json(report);
    let trimmed = report_json.trim_end();
    trimmed
        .strip_suffix('}')
        .map(|prefix| format!("{prefix},\n  \"diff\": {diff_json}\n}}\n"))
}

fn is_diff_report_artifact(report_json: &str) -> bool {
    contains_json_string_field(report_json, "schema_id", REPORT_SCHEMA_ID)
        && contains_json_string_field(report_json, "command", REPORT_COMMAND_DIFF)
}

fn contains_json_string_field(json: &str, field: &str, value: &str) -> bool {
    let spaced = format!("\"{field}\": \"{value}\"");
    let compact = format!("\"{field}\":\"{value}\"");
    json.contains(&spaced) || json.contains(&compact)
}

pub(crate) fn render_diff_posture_json(report: DiffReport<'_>) -> String {
    render_diff_posture_json_with_evidence_health(report, 0, 0, 0)
}

pub(crate) fn render_diff_posture_json_with_evidence_health(
    report: DiffReport<'_>,
    broken_evidence_links: usize,
    missing_evidence: usize,
    weak_evidence_references: usize,
) -> String {
    let mut out = String::new();
    out.push_str("{\n");
    out.push_str(&format!(
        "    \"net_posture\": \"{}\",\n",
        json_escape(report.net_posture)
    ));
    out.push_str(&format!(
        "    \"reviewer_action\": \"{}\",\n",
        json_escape(report.reviewer_action)
    ));
    out.push_str("    \"summary\": {\n");
    out.push_str(&format!(
        "      \"current_failures\": {},\n",
        report.summary.current_failures
    ));
    if broken_evidence_links > 0 {
        out.push_str(&format!(
            "      \"broken_evidence_links\": {},\n",
            broken_evidence_links
        ));
    }
    if missing_evidence > 0 {
        out.push_str(&format!(
            "      \"missing_evidence\": {},\n",
            missing_evidence
        ));
    }
    if weak_evidence_references > 0 {
        out.push_str(&format!(
            "      \"weak_evidence_references\": {},\n",
            weak_evidence_references
        ));
    }
    let structural_delta = diff_structural_delta_summary(report.policy_changes);
    if structural_delta.scope_broadened > 0 {
        out.push_str(&format!(
            "      \"scope_broadened\": {},\n",
            structural_delta.scope_broadened
        ));
    }
    if structural_delta.scope_changed > 0 {
        out.push_str(&format!(
            "      \"scope_changed\": {},\n",
            structural_delta.scope_changed
        ));
    }
    if structural_delta.scope_narrowed > 0 {
        out.push_str(&format!(
            "      \"scope_narrowed\": {},\n",
            structural_delta.scope_narrowed
        ));
    }
    if structural_delta.selector_changed > 0 {
        out.push_str(&format!(
            "      \"selector_changed\": {},\n",
            structural_delta.selector_changed
        ));
    }
    if structural_delta.selector_precision_decreased > 0 {
        out.push_str(&format!(
            "      \"selector_precision_decreased\": {},\n",
            structural_delta.selector_precision_decreased
        ));
    }
    if structural_delta.selector_precision_increased > 0 {
        out.push_str(&format!(
            "      \"selector_precision_increased\": {},\n",
            structural_delta.selector_precision_increased
        ));
    }
    let evidence_delta = diff_evidence_delta_summary(report.policy_changes);
    if evidence_delta.evidence_added > 0 {
        out.push_str(&format!(
            "      \"evidence_added\": {},\n",
            evidence_delta.evidence_added
        ));
    }
    if evidence_delta.weak_evidence_added > 0 {
        out.push_str(&format!(
            "      \"weak_evidence_added\": {},\n",
            evidence_delta.weak_evidence_added
        ));
    }
    if evidence_delta.broken_evidence_added > 0 {
        out.push_str(&format!(
            "      \"broken_evidence_added\": {},\n",
            evidence_delta.broken_evidence_added
        ));
    }
    if evidence_delta.evidence_removed > 0 {
        out.push_str(&format!(
            "      \"evidence_removed\": {},\n",
            evidence_delta.evidence_removed
        ));
    }
    if evidence_delta.evidence_removal_failures > 0 {
        out.push_str(&format!(
            "      \"evidence_removal_failures\": {},\n",
            evidence_delta.evidence_removal_failures
        ));
    }
    if evidence_delta.evidence_removal_review_items > 0 {
        out.push_str(&format!(
            "      \"evidence_removal_review_items\": {},\n",
            evidence_delta.evidence_removal_review_items
        ));
    }
    if evidence_delta.evidence_removal_improvements > 0 {
        out.push_str(&format!(
            "      \"evidence_removal_improvements\": {},\n",
            evidence_delta.evidence_removal_improvements
        ));
    }
    if evidence_delta.link_added > 0 {
        out.push_str(&format!(
            "      \"link_added\": {},\n",
            evidence_delta.link_added
        ));
    }
    if evidence_delta.weak_link_added > 0 {
        out.push_str(&format!(
            "      \"weak_link_added\": {},\n",
            evidence_delta.weak_link_added
        ));
    }
    if evidence_delta.broken_link_added > 0 {
        out.push_str(&format!(
            "      \"broken_link_added\": {},\n",
            evidence_delta.broken_link_added
        ));
    }
    if evidence_delta.link_removed > 0 {
        out.push_str(&format!(
            "      \"link_removed\": {},\n",
            evidence_delta.link_removed
        ));
    }
    if evidence_delta.link_removal_failures > 0 {
        out.push_str(&format!(
            "      \"link_removal_failures\": {},\n",
            evidence_delta.link_removal_failures
        ));
    }
    if evidence_delta.link_removal_review_items > 0 {
        out.push_str(&format!(
            "      \"link_removal_review_items\": {},\n",
            evidence_delta.link_removal_review_items
        ));
    }
    if evidence_delta.link_removal_improvements > 0 {
        out.push_str(&format!(
            "      \"link_removal_improvements\": {},\n",
            evidence_delta.link_removal_improvements
        ));
    }
    out.push_str(&format!(
        "      \"new_findings\": {},\n",
        report.summary.new_findings
    ));
    out.push_str(&format!(
        "      \"removed_findings\": {},\n",
        report.summary.removed_findings
    ));
    out.push_str(&format!(
        "      \"policy_failures\": {},\n",
        report.summary.policy_failures
    ));
    out.push_str(&format!(
        "      \"policy_review_items\": {},\n",
        report.summary.policy_review_items
    ));
    out.push_str(&format!(
        "      \"policy_improvements\": {}\n",
        report.summary.policy_improvements
    ));
    out.push_str("    },\n");
    out.push_str("    \"finding_changes\": [\n");
    for (index, change) in report.finding_changes.iter().enumerate() {
        if index > 0 {
            out.push_str(",\n");
        }
        out.push_str("      {");
        out.push_str(&format!("\"change\": \"{}\", ", json_escape(change.change)));
        out.push_str(&format!("\"key\": \"{}\", ", json_escape(change.key)));
        out.push_str(&format!("\"kind\": \"{}\", ", json_escape(change.kind)));
        out.push_str(&format!("\"family\": {}, ", option_json(change.family)));
        out.push_str(&format!("\"path\": \"{}\"", json_escape(change.path)));
        if let Some(line) = change.line {
            out.push_str(&format!(", \"line\": {line}"));
        }
        if let Some(column) = change.column {
            out.push_str(&format!(", \"column\": {column}"));
        }
        if let Some(source_package) = change.source_package {
            out.push_str(&format!(
                ", \"source_package\": \"{}\"",
                json_escape(source_package)
            ));
        }
        if let Some(identity) = change.identity {
            out.push_str(", \"identity\": ");
            out.push_str(&structural_identity_json(identity, "    "));
        }
        out.push('}');
    }
    out.push_str("\n    ],\n");
    out.push_str("    \"policy_changes\": [\n");
    for (index, change) in report.policy_changes.iter().enumerate() {
        if index > 0 {
            out.push_str(",\n");
        }
        out.push_str("      {");
        out.push_str(&format!(
            "\"severity\": \"{}\", ",
            json_escape(change.severity)
        ));
        out.push_str(&format!(
            "\"allow_id\": \"{}\", ",
            json_escape(change.allow_id)
        ));
        out.push_str(&format!("\"kind\": \"{}\", ", json_escape(change.kind)));
        out.push_str(&format!("\"message\": \"{}\"", json_escape(change.message)));
        if let Some(exception_identity) = change.exception_identity {
            out.push_str(", ");
            out.push_str(&format!(
                "\"exception_identity\": {{\"field\": \"{}\", \"before\": {}, \"after\": {}}}",
                json_escape(exception_identity.field),
                option_json(exception_identity.before),
                option_json(exception_identity.after)
            ));
        }
        if let Some(selector_identity) = change.selector_identity {
            out.push_str(", ");
            out.push_str(&format!(
                "\"selector_identity\": {{\"changed_fields\": {}}}",
                json_string_array(selector_identity.changed_fields)
            ));
        }
        if let Some(selector_precision) = change.selector_precision {
            out.push_str(", ");
            out.push_str(&format!(
                "\"selector_precision\": {{\"before\": {}, \"after\": {}, \"removed_fields\": {}, \"added_fields\": {}}}",
                selector_precision.before,
                selector_precision.after,
                json_string_array(selector_precision.removed_fields),
                json_string_array(selector_precision.added_fields)
            ));
        }
        if let Some(scope) = change.scope {
            out.push_str(", ");
            out.push_str(&format!(
                "\"scope\": {{\"field\": \"{}\", \"before\": {}, \"after\": {}}}",
                json_escape(scope.field),
                option_json(scope.before),
                option_json(scope.after)
            ));
        }
        if let Some(limit) = change.occurrence_limit {
            out.push_str(", ");
            out.push_str(&format!(
                "\"occurrence_limit\": {{\"before\": {}, \"after\": {}}}",
                option_u32_json(limit.before),
                option_u32_json(limit.after)
            ));
        }
        if let Some(lifecycle) = change.lifecycle {
            out.push_str(", ");
            out.push_str(&format!(
                "\"lifecycle\": {{\"field\": \"{}\", \"before\": {}, \"after\": {}}}",
                json_escape(lifecycle.field),
                option_json(lifecycle.before),
                option_json(lifecycle.after)
            ));
        }
        if let Some(evidence) = change.evidence {
            out.push_str(", ");
            out.push_str(&format!(
                "\"evidence\": {{\"field\": \"{}\", \"removed\": {}, \"added\": {}}}",
                json_escape(evidence.field),
                json_string_array(evidence.removed),
                json_string_array(evidence.added)
            ));
        }
        if let Some(metadata) = change.metadata {
            out.push_str(", ");
            out.push_str(&format!(
                "\"metadata\": {{\"field\": \"{}\", \"before\": {}, \"after\": {}}}",
                json_escape(metadata.field),
                option_json(metadata.before),
                option_json(metadata.after)
            ));
        }
        if let Some(requirement) = change.requirement {
            out.push_str(", ");
            out.push_str(&format!(
                "\"requirement\": {{\"field\": \"{}\", \"before\": {}, \"after\": {}}}",
                json_escape(requirement.field),
                requirement.before,
                requirement.after
            ));
        }
        if let Some(policy_status) = change.policy_status {
            out.push_str(", ");
            out.push_str(&format!(
                "\"policy_status\": {{\"before\": {}, \"after\": {}}}",
                option_json(policy_status.before),
                option_json(policy_status.after)
            ));
        }
        out.push('}');
    }
    out.push_str("\n    ]\n");
    out.push_str("  }");
    out
}

fn option_u32_json(value: Option<u32>) -> String {
    value.map_or_else(|| "null".to_string(), |value| value.to_string())
}