1use crate::contracts::ADD_ARTIFACT;
2use crate::json::{bool_json, option_json, push_json_fixed_artifact_preamble};
3use crate::{
4 AddReport, CLAIM_BOUNDARY_TEXT, finding_location_text, render_explain_finding_json,
5 render_last_seen_json, render_selector_json,
6};
7use allow_core::{json_escape, normalize_path};
8
9pub fn render_add_human(report: AddReport<'_>) -> String {
10 let entry = report.entry;
11 let selected_finding = report.selected_finding;
12 let mut out = String::new();
13 out.push_str("cargo-allow add summary\n");
14 out.push_str(&format!(
15 "inventory: {}/{} via {}{}\n",
16 report.inventory.scope,
17 report.inventory.scanner,
18 report.inventory.source,
19 add_inventory_files_suffix(report.inventory)
20 ));
21 if let Some(root) = report.inventory.root {
22 out.push_str(&format!("source_tree_root: {root}\n"));
23 }
24 out.push_str(&format!("id: {}\n", entry.id));
25 out.push_str(&format!("kind: {}\n", entry.kind));
26 if let Some(family) = &entry.family {
27 out.push_str(&format!("family: {family}\n"));
28 }
29 out.push_str(&format!("scope: {}\n", entry.path_or_glob()));
30 out.push_str(&format!("owner: {}\n", entry.owner));
31 out.push_str(&format!("classification: {}\n", entry.classification));
32 out.push_str(&format!(
33 "matched finding: {}\n",
34 finding_location_text(selected_finding)
35 ));
36 if let Some(output) = report.policy_output {
37 out.push_str(&format!("output: {output}\n"));
38 } else {
39 out.push_str("output: stdout\n");
40 }
41 out.push_str("claim boundary: generated policy entry requires human review before merge.\n");
42 out.push_str(CLAIM_BOUNDARY_TEXT);
43 out.push('\n');
44 out
45}
46
47fn add_inventory_files_suffix(inventory: crate::InventoryContext<'_>) -> String {
48 inventory
49 .files_scanned
50 .map(|files| format!("; files scanned: {files}"))
51 .unwrap_or_default()
52}
53
54pub fn render_add_json(report: AddReport<'_>) -> String {
55 let entry = report.entry;
56 let selected_finding = report.selected_finding;
57 let path = entry.path.as_ref().map(normalize_path);
58 let mut out = String::new();
59 out.push_str("{\n");
60 push_json_fixed_artifact_preamble(&mut out, ADD_ARTIFACT, report.inventory);
61 out.push_str(" \"options\": {\n");
62 out.push_str(&format!(
63 " \"policy_output\": {},\n",
64 option_json(report.policy_output)
65 ));
66 out.push_str(&format!(" \"force\": {}\n", bool_json(report.force)));
67 out.push_str(" },\n");
68 out.push_str(" \"summary\": {\n");
69 out.push_str(&format!(
70 " \"entry_id\": \"{}\",\n",
71 json_escape(&entry.id)
72 ));
73 out.push_str(&format!(
74 " \"selected_finding\": \"{}\",\n",
75 json_escape(&finding_location_text(selected_finding))
76 ));
77 out.push_str(" \"human_review_required\": true\n");
78 out.push_str(" },\n");
79 out.push_str(" \"allow_entry\": {\n");
80 out.push_str(&format!(" \"id\": \"{}\",\n", json_escape(&entry.id)));
81 out.push_str(&format!(" \"kind\": \"{}\",\n", entry.kind));
82 out.push_str(&format!(
83 " \"family\": {},\n",
84 option_json(entry.family.as_deref())
85 ));
86 out.push_str(&format!(
87 " \"path\": {},\n",
88 option_json(path.as_deref())
89 ));
90 out.push_str(&format!(
91 " \"glob\": {},\n",
92 option_json(entry.glob.as_deref())
93 ));
94 out.push_str(&format!(
95 " \"owner\": \"{}\",\n",
96 json_escape(&entry.owner)
97 ));
98 out.push_str(&format!(
99 " \"classification\": \"{}\",\n",
100 json_escape(&entry.classification)
101 ));
102 out.push_str(&format!(
103 " \"reason\": \"{}\",\n",
104 json_escape(&entry.reason)
105 ));
106 out.push_str(&format!(
107 " \"review_after\": {},\n",
108 option_json(entry.lifecycle.review_after.as_deref())
109 ));
110 out.push_str(&format!(
111 " \"expires\": {},\n",
112 option_json(entry.lifecycle.expires.as_deref())
113 ));
114 out.push_str(&format!(
115 " \"evidence_count\": {},\n",
116 entry.evidence.len()
117 ));
118 out.push_str(" \"selector\": ");
119 out.push_str(&render_selector_json(&entry.selector, " "));
120 out.push_str(",\n");
121 out.push_str(" \"last_seen\": ");
122 out.push_str(&render_last_seen_json(entry.last_seen.as_ref(), " "));
123 out.push_str("\n },\n");
124 out.push_str(" \"selected_finding\": ");
125 out.push_str(&render_explain_finding_json(
126 selected_finding,
127 "selected",
128 " ",
129 ));
130 out.push_str("\n}\n");
131 out
132}