Skip to main content

allow_report/
add.rs

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}