Skip to main content

allow_report/
receipt.rs

1use crate::contracts::RECEIPT_ARTIFACT;
2use crate::evidence_repair::{
3    evidence_repair_queues_from_context, push_evidence_repair_queue_json_fields,
4};
5use crate::json::{
6    push_json_artifact_header, push_json_artifact_source_context, push_json_receipt_run_metadata,
7    push_json_status_fields, push_json_status_fields_with_status,
8};
9use crate::{
10    ARTIFACT_STATUS_ERROR, RECEIPT_COMMAND_CHECK, ReportContext, Summary, baseline_debt_count,
11    render_count_fields_with_policy_context, render_source_inventory_json,
12};
13use allow_core::{Finding, MatchOutcome, json_escape};
14
15pub fn render_receipt(command: &str, outcomes: &[MatchOutcome], failed: bool) -> String {
16    render_receipt_with_context(command, outcomes, failed, ReportContext::default())
17}
18
19pub fn render_receipt_with_context(
20    command: &str,
21    outcomes: &[MatchOutcome],
22    failed: bool,
23    context: ReportContext<'_>,
24) -> String {
25    render_receipt_json(command, None, outcomes, failed, context)
26}
27
28pub fn render_receipt_with_context_and_inventory(
29    command: &str,
30    findings: &[Finding],
31    outcomes: &[MatchOutcome],
32    failed: bool,
33    context: ReportContext<'_>,
34) -> String {
35    render_receipt_json(command, Some(findings), outcomes, failed, context)
36}
37
38pub fn render_error_receipt(diagnostic: &str, context: ReportContext<'_>) -> String {
39    let mut out = String::new();
40    out.push_str("{\n");
41    push_json_artifact_header(&mut out, RECEIPT_ARTIFACT, RECEIPT_COMMAND_CHECK);
42    push_json_status_fields_with_status(&mut out, ARTIFACT_STATUS_ERROR, true);
43    push_json_receipt_run_metadata(&mut out, context);
44    out.push_str(&format!(
45        "  \"diagnostic\": \"{}\",\n",
46        json_escape(diagnostic)
47    ));
48    push_json_artifact_source_context(&mut out, context.into());
49    out.push_str("  \"counts\": {\n");
50    out.push_str(&render_count_fields_with_policy_context(
51        &Summary::default(),
52        None,
53        None,
54        None,
55        None,
56        "    ",
57    ));
58    out.push_str("  }\n");
59    out.push_str("}\n");
60    out
61}
62
63fn render_receipt_json(
64    command: &str,
65    findings: Option<&[Finding]>,
66    outcomes: &[MatchOutcome],
67    failed: bool,
68    context: ReportContext<'_>,
69) -> String {
70    assert_eq!(
71        command, RECEIPT_COMMAND_CHECK,
72        "receipt artifacts support only the check command"
73    );
74    let summary = Summary::from_outcomes(outcomes);
75    let mut out = String::new();
76    out.push_str("{\n");
77    push_json_artifact_header(&mut out, RECEIPT_ARTIFACT, command);
78    push_json_status_fields(&mut out, failed);
79    push_json_receipt_run_metadata(&mut out, context);
80    push_json_artifact_source_context(&mut out, context.into());
81    out.push_str("  \"counts\": {\n");
82    out.push_str(&render_count_fields_with_policy_context(
83        &summary,
84        Some(baseline_debt_count(&summary, context)),
85        context.policy_missing_evidence_entries,
86        context.broken_evidence_links,
87        context.weak_evidence_references,
88        "    ",
89    ));
90    out.push_str("  }");
91    append_evidence_repair_queues_json(&summary, context, &mut out);
92    if let Some(source_inventory) =
93        findings.and_then(|findings| render_source_inventory_json(findings, outcomes, "  "))
94    {
95        out.push_str(",\n  \"source_inventory\": ");
96        out.push_str(&source_inventory);
97        out.push('\n');
98    } else {
99        out.push('\n');
100    }
101    out.push_str("}\n");
102    out
103}
104
105fn append_evidence_repair_queues_json(
106    summary: &Summary,
107    context: ReportContext<'_>,
108    out: &mut String,
109) {
110    let queues = evidence_repair_queues_from_context(summary, context);
111    if queues.is_empty() {
112        return;
113    }
114
115    out.push_str(",\n  \"evidence_repair_queues\": [\n");
116    for (index, queue) in queues.iter().enumerate() {
117        if index > 0 {
118            out.push_str(",\n");
119        }
120        out.push_str("    {\n");
121        push_evidence_repair_queue_json_fields(out, queue, "      ");
122        out.push_str("    }");
123    }
124    out.push_str("\n  ]");
125}