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
26fn 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 out.push_str(" \"current_findings\": [\n");
70 for (index, finding) in report.current_findings.iter().enumerate() {
71 if index > 0 {
72 out.push_str(",\n");
73 }
74 let status = report
75 .match_outcomes
76 .iter()
77 .find(|outcome| outcome.finding_index == Some(index))
78 .map(|outcome| outcome.status.as_str())
79 .unwrap_or("unmatched");
80 out.push_str(&render_explain_finding_json(finding, status, " "));
81 }
82 out.push_str("\n ],\n");
83 out.push_str(" \"match_outcomes\": [\n");
84 for (index, outcome) in report.match_outcomes.iter().enumerate() {
85 if index > 0 {
86 out.push_str(",\n");
87 }
88 out.push_str(&render_match_outcome_json(outcome, " "));
89 }
90 out.push_str("\n ],\n");
91 out.push_str(" \"next\": {\n");
92 out.push_str(&format!(
93 " \"suggested_actions\": {},\n",
94 json_string_array(report.suggested_actions)
95 ));
96 out.push_str(&format!(
97 " \"proof_commands\": {}\n",
98 json_string_array(report.proof_commands)
99 ));
100 out.push_str(" }\n");
101 out.push_str("}\n");
102 out
103}
104
105fn render_evidence_reference_json(reference: &EvidenceReference<'_>, indent: &str) -> String {
106 format!(
107 "{indent} {{\n{indent} \"raw\": \"{}\",\n{indent} \"prefix\": {},\n{indent} \"target\": {},\n{indent} \"status\": \"{}\",\n{indent} \"message\": \"{}\"\n{indent} }}",
108 json_escape(reference.raw),
109 option_json(reference.prefix),
110 option_json(reference.target),
111 json_escape(reference.status),
112 json_escape(reference.message)
113 )
114}