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}