Skip to main content

allow_report/
explain_json.rs

1use crate::contracts::EXPLAIN_ARTIFACT;
2use crate::explain_common::explain_report_status;
3use crate::json::{
4    json_string_array, option_json, option_u32_json, push_json_fixed_artifact_preamble,
5    render_match_outcome_json,
6};
7use crate::{EvidenceReference, ExplainReport, render_allow_entry_json};
8use allow_core::{Finding, StructuralIdentity, json_escape, normalize_path};
9
10pub fn render_explain_finding_json(finding: &Finding, status: &str, indent: &str) -> String {
11    let span = finding.span.as_ref();
12    format!(
13        "{indent}  {{\n{indent}    \"status\": \"{}\",\n{indent}    \"kind\": \"{}\",\n{indent}    \"family\": {},\n{indent}    \"path\": \"{}\",\n{indent}    \"line\": {},\n{indent}    \"column\": {},\n{indent}    \"source_package\": {},\n{indent}    \"identity\": {},\n{indent}    \"message\": \"{}\"\n{indent}  }}",
14        json_escape(status),
15        finding.kind,
16        option_json(finding.family.as_deref()),
17        json_escape(&normalize_path(&finding.path)),
18        option_u32_json(span.map(|span| span.line)),
19        option_u32_json(span.map(|span| span.column)),
20        option_json(finding.source_package_name()),
21        structural_identity_json(&finding.identity, indent),
22        json_escape(&finding.message)
23    )
24}
25
26pub(crate) fn structural_identity_json(identity: &StructuralIdentity, indent: &str) -> String {
27    format!(
28        "{{\n{indent}      \"language\": \"{}\",\n{indent}      \"crate_name\": {},\n{indent}      \"module\": {},\n{indent}      \"container\": {},\n{indent}      \"ast_kind\": \"{}\",\n{indent}      \"symbol\": {},\n{indent}      \"callee\": {},\n{indent}      \"macro_name\": {},\n{indent}      \"lint\": {},\n{indent}      \"receiver_fingerprint\": {},\n{indent}      \"target_fingerprint\": {},\n{indent}      \"normalized_snippet_hash\": {},\n{indent}      \"line_hint\": {},\n{indent}      \"column_hint\": {}\n{indent}    }}",
29        json_escape(&identity.language),
30        option_json(identity.crate_name.as_deref()),
31        option_json(identity.module.as_deref()),
32        option_json(identity.container.as_deref()),
33        json_escape(&identity.ast_kind),
34        option_json(identity.symbol.as_deref()),
35        option_json(identity.callee.as_deref()),
36        option_json(identity.macro_name.as_deref()),
37        option_json(identity.lint.as_deref()),
38        option_json(identity.receiver_fingerprint.as_deref()),
39        option_json(identity.target_fingerprint.as_deref()),
40        option_json(identity.normalized_snippet_hash.as_deref()),
41        option_u32_json(identity.line_hint),
42        option_u32_json(identity.column_hint)
43    )
44}
45
46pub fn render_explain_json(report: ExplainReport<'_>) -> String {
47    let mut out = String::new();
48    out.push_str("{\n");
49    push_json_fixed_artifact_preamble(&mut out, EXPLAIN_ARTIFACT, report.inventory);
50    out.push_str("  \"allow_entry\": ");
51    out.push_str(&render_allow_entry_json(report.entry, "  "));
52    out.push_str(",\n");
53    out.push_str(&format!(
54        "  \"summary\": {{\n    \"current_status\": \"{}\",\n    \"current_matches\": {},\n    \"match_outcomes\": {},\n    \"selector_precision\": {},\n    \"broad_scope\": {}\n  }},\n",
55        explain_report_status(report.match_outcomes).as_str(),
56        report.current_findings.len(),
57        report.match_outcomes.len(),
58        report.selector_precision,
59        crate::json::bool_json(report.broad_scope)
60    ));
61    out.push_str("  \"evidence_references\": [\n");
62    for (index, diagnostic) in report.evidence_references.iter().enumerate() {
63        if index > 0 {
64            out.push_str(",\n");
65        }
66        out.push_str(&render_evidence_reference_json(diagnostic, "  "));
67    }
68    out.push_str("\n  ],\n");
69    if !report.link_references.is_empty() {
70        out.push_str("  \"link_references\": [\n");
71        for (index, diagnostic) in report.link_references.iter().enumerate() {
72            if index > 0 {
73                out.push_str(",\n");
74            }
75            out.push_str(&render_evidence_reference_json(diagnostic, "  "));
76        }
77        out.push_str("\n  ],\n");
78    }
79    out.push_str("  \"current_findings\": [\n");
80    for (index, finding) in report.current_findings.iter().enumerate() {
81        if index > 0 {
82            out.push_str(",\n");
83        }
84        let status = report
85            .match_outcomes
86            .iter()
87            .find(|outcome| outcome.finding_index == Some(index))
88            .map(|outcome| outcome.status.as_str())
89            .unwrap_or("unmatched");
90        out.push_str(&render_explain_finding_json(finding, status, "  "));
91    }
92    out.push_str("\n  ],\n");
93    out.push_str("  \"match_outcomes\": [\n");
94    for (index, outcome) in report.match_outcomes.iter().enumerate() {
95        if index > 0 {
96            out.push_str(",\n");
97        }
98        out.push_str(&render_match_outcome_json(outcome, "  "));
99    }
100    out.push_str("\n  ],\n");
101    out.push_str("  \"next\": {\n");
102    out.push_str(&format!(
103        "    \"suggested_actions\": {},\n",
104        json_string_array(report.suggested_actions)
105    ));
106    out.push_str(&format!(
107        "    \"proof_commands\": {}\n",
108        json_string_array(report.proof_commands)
109    ));
110    out.push_str("  }\n");
111    out.push_str("}\n");
112    out
113}
114
115fn render_evidence_reference_json(reference: &EvidenceReference<'_>, indent: &str) -> String {
116    format!(
117        "{indent}  {{\n{indent}    \"raw\": \"{}\",\n{indent}    \"prefix\": {},\n{indent}    \"target\": {},\n{indent}    \"status\": \"{}\",\n{indent}    \"category\": \"{}\",\n{indent}    \"message\": \"{}\"\n{indent}  }}",
118        json_escape(reference.raw),
119        option_json(reference.prefix),
120        option_json(reference.target),
121        json_escape(reference.status),
122        json_escape(reference.category),
123        json_escape(reference.message)
124    )
125}