Skip to main content

allow_report/
migrate.rs

1use crate::contracts::MIGRATE_ARTIFACT;
2use crate::evidence_repair::{
3    BROKEN_EVIDENCE_LINK_COMMAND, WEAK_EVIDENCE_REFERENCE_COMMAND,
4    evidence_repair_queues_from_counts,
5};
6use crate::json::{bool_json, push_json_fixed_artifact_preamble};
7use crate::{CLAIM_BOUNDARY_TEXT, MigrateReport};
8use allow_core::json_escape;
9
10const UNSAFE_BROKEN_EVIDENCE_LINK_COMMAND: &str =
11    "cargo-allow worklist --item-kind broken_evidence_link --kind unsafe --format json";
12const UNSAFE_WEAK_EVIDENCE_REFERENCE_COMMAND: &str =
13    "cargo-allow worklist --item-kind weak_evidence_reference --kind unsafe --format json";
14
15pub fn render_migrate_human(report: MigrateReport<'_>) -> String {
16    let mut out = String::new();
17    out.push_str("cargo-allow migrate summary\n");
18    out.push_str(&format!("input_kind: {}\n", report.input_kind));
19    out.push_str(&format!("input: {}\n", report.input_path));
20    out.push_str(&format!("output: {}\n", report.output_path));
21    out.push_str(&format!("force: {}\n", report.force));
22    out.push_str(&format!("allow_entries: {}\n", report.allow_entries));
23    out.push_str(&format!("baseline_debt: {}\n", report.baseline_debt));
24    out.push_str(&format!("unsafe_entries: {}\n", report.unsafe_entries));
25    out.push_str(&format!(
26        "lint_exception_entries: {}\n",
27        report.lint_exception_entries
28    ));
29    out.push_str(&format!(
30        "entries_with_evidence: {}\n",
31        report.entries_with_evidence
32    ));
33    if let Some(count) = report.broken_evidence_links.filter(|count| *count > 0) {
34        out.push_str(&format!("broken_evidence_links: {count}\n"));
35    }
36    if let Some(count) = report
37        .unsafe_broken_evidence_links
38        .filter(|count| *count > 0)
39    {
40        out.push_str(&format!("unsafe_broken_evidence_links: {count}\n"));
41    }
42    if let Some(count) = report.weak_evidence_references.filter(|count| *count > 0) {
43        out.push_str(&format!("weak_evidence_references: {count}\n"));
44    }
45    if let Some(count) = report
46        .unsafe_weak_evidence_references
47        .filter(|count| *count > 0)
48    {
49        out.push_str(&format!("unsafe_weak_evidence_references: {count}\n"));
50    }
51    append_migrate_evidence_repair_queues_human(report, &mut out);
52    out.push_str(&format!(
53        "inventory: {}/{} via {}{}\n",
54        report.inventory.scope,
55        report.inventory.scanner,
56        report.inventory.source,
57        migrate_inventory_files_suffix(report.inventory)
58    ));
59    if let Some(root) = report.inventory.root {
60        out.push_str(&format!("source_tree_root: {root}\n"));
61    }
62    out.push_str(report.notes);
63    if !report.notes.ends_with('\n') {
64        out.push('\n');
65    }
66    out.push_str(CLAIM_BOUNDARY_TEXT);
67    out.push('\n');
68    out
69}
70
71fn append_migrate_evidence_repair_queues_human(report: MigrateReport<'_>, out: &mut String) {
72    let commands = migrate_evidence_repair_commands(report);
73    if commands.is_empty() {
74        return;
75    }
76    out.push_str("evidence_repair_queues:\n");
77    for command in commands {
78        out.push_str(&format!("  {command}\n"));
79    }
80}
81
82fn migrate_evidence_repair_commands(report: MigrateReport<'_>) -> Vec<&'static str> {
83    let mut commands = Vec::new();
84    if report.broken_evidence_links.unwrap_or(0) > 0
85        || report.unsafe_broken_evidence_links.unwrap_or(0) > 0
86    {
87        commands.push(BROKEN_EVIDENCE_LINK_COMMAND);
88    }
89    if report.unsafe_broken_evidence_links.unwrap_or(0) > 0 {
90        commands.push(UNSAFE_BROKEN_EVIDENCE_LINK_COMMAND);
91    }
92    if report.weak_evidence_references.unwrap_or(0) > 0
93        || report.unsafe_weak_evidence_references.unwrap_or(0) > 0
94    {
95        commands.push(WEAK_EVIDENCE_REFERENCE_COMMAND);
96    }
97    if report.unsafe_weak_evidence_references.unwrap_or(0) > 0 {
98        commands.push(UNSAFE_WEAK_EVIDENCE_REFERENCE_COMMAND);
99    }
100    commands
101}
102
103fn migrate_inventory_files_suffix(inventory: crate::InventoryContext<'_>) -> String {
104    inventory
105        .files_scanned
106        .map(|files| format!("; files scanned: {files}"))
107        .unwrap_or_default()
108}
109
110pub fn render_migrate_json(report: MigrateReport<'_>) -> String {
111    let mut out = String::new();
112    out.push_str("{\n");
113    push_json_fixed_artifact_preamble(&mut out, MIGRATE_ARTIFACT, report.inventory);
114    out.push_str("  \"input\": {\n");
115    out.push_str(&format!(
116        "    \"kind\": \"{}\",\n",
117        json_escape(report.input_kind)
118    ));
119    out.push_str(&format!(
120        "    \"path\": \"{}\"\n",
121        json_escape(report.input_path)
122    ));
123    out.push_str("  },\n");
124    out.push_str("  \"output\": {\n");
125    out.push_str(&format!(
126        "    \"path\": \"{}\",\n",
127        json_escape(report.output_path)
128    ));
129    out.push_str(&format!("    \"force\": {}\n", bool_json(report.force)));
130    out.push_str("  },\n");
131    out.push_str("  \"summary\": {\n");
132    out.push_str(&format!(
133        "    \"allow_entries\": {},\n",
134        report.allow_entries
135    ));
136    out.push_str(&format!(
137        "    \"baseline_debt\": {},\n",
138        report.baseline_debt
139    ));
140    out.push_str(&format!(
141        "    \"unsafe_entries\": {},\n",
142        report.unsafe_entries
143    ));
144    out.push_str(&format!(
145        "    \"lint_exception_entries\": {},\n",
146        report.lint_exception_entries
147    ));
148    let mut summary_tail = vec![format!(
149        "    \"entries_with_evidence\": {}",
150        report.entries_with_evidence
151    )];
152    for (name, count) in [
153        ("broken_evidence_links", report.broken_evidence_links),
154        (
155            "unsafe_broken_evidence_links",
156            report.unsafe_broken_evidence_links,
157        ),
158        ("weak_evidence_references", report.weak_evidence_references),
159        (
160            "unsafe_weak_evidence_references",
161            report.unsafe_weak_evidence_references,
162        ),
163    ] {
164        if let Some(count) = count.filter(|count| *count > 0) {
165            summary_tail.push(format!("    \"{name}\": {count}"));
166        }
167    }
168    out.push_str(&summary_tail.join(",\n"));
169    out.push('\n');
170    out.push_str("  },\n");
171    append_migrate_evidence_repair_queues_json(report, &mut out);
172    out.push_str(&format!("  \"notes\": \"{}\"\n", json_escape(report.notes)));
173    out.push_str("}\n");
174    out
175}
176
177fn append_migrate_evidence_repair_queues_json(report: MigrateReport<'_>, out: &mut String) {
178    let queues = migrate_evidence_repair_queues(report);
179    if queues.is_empty() {
180        return;
181    }
182
183    out.push_str("  \"evidence_repair_queues\": [\n");
184    for (index, queue) in queues.iter().enumerate() {
185        if index > 0 {
186            out.push_str(",\n");
187        }
188        out.push_str("    {\n");
189        out.push_str(&format!(
190            "      \"signal\": \"{}\",\n",
191            json_escape(queue.signal)
192        ));
193        out.push_str(&format!(
194            "      \"label\": \"{}\",\n",
195            json_escape(queue.label)
196        ));
197        out.push_str(&format!(
198            "      \"route_kind\": \"{}\",\n",
199            json_escape(queue.route_kind)
200        ));
201        out.push_str(&format!(
202            "      \"item_kind\": \"{}\",\n",
203            json_escape(queue.item_kind)
204        ));
205        out.push_str(&format!("      \"count\": {},\n", queue.count));
206        out.push_str(&format!(
207            "      \"unsafe_count\": {},\n",
208            queue.unsafe_count
209        ));
210        out.push_str(&format!(
211            "      \"command\": \"{}\"",
212            json_escape(queue.command)
213        ));
214        if let Some(unsafe_command) = queue.unsafe_command {
215            out.push_str(",\n");
216            out.push_str(&format!(
217                "      \"unsafe_command\": \"{}\"",
218                json_escape(unsafe_command)
219            ));
220        }
221        out.push('\n');
222        out.push_str("    }");
223    }
224    out.push_str("\n  ],\n");
225}
226
227fn migrate_evidence_repair_queues(report: MigrateReport<'_>) -> Vec<MigrateEvidenceRepairQueue> {
228    let mut queues = Vec::new();
229    let broken_count = report.broken_evidence_links.unwrap_or(0);
230    let unsafe_broken_count = report.unsafe_broken_evidence_links.unwrap_or(0);
231    let weak_count = report.weak_evidence_references.unwrap_or(0);
232    let unsafe_weak_count = report.unsafe_weak_evidence_references.unwrap_or(0);
233    let broken_total = broken_count.max(unsafe_broken_count);
234    let weak_total = weak_count.max(unsafe_weak_count);
235    for queue in evidence_repair_queues_from_counts(broken_total, 0, weak_total) {
236        let Some(item_kind) = queue.item_kind else {
237            continue;
238        };
239        let (count, unsafe_count) = match item_kind {
240            "broken_evidence_link" => (broken_total, unsafe_broken_count),
241            "weak_evidence_reference" => (weak_total, unsafe_weak_count),
242            _ => (queue.count, 0),
243        };
244        let unsafe_command = match item_kind {
245            "broken_evidence_link" if unsafe_count > 0 => Some(UNSAFE_BROKEN_EVIDENCE_LINK_COMMAND),
246            "weak_evidence_reference" if unsafe_count > 0 => {
247                Some(UNSAFE_WEAK_EVIDENCE_REFERENCE_COMMAND)
248            }
249            _ => None,
250        };
251        queues.push(MigrateEvidenceRepairQueue {
252            signal: queue.signal,
253            label: queue.label,
254            route_kind: "worklist_item_kind",
255            item_kind,
256            count,
257            unsafe_count,
258            command: queue.command,
259            unsafe_command,
260        });
261    }
262    queues
263}
264
265struct MigrateEvidenceRepairQueue {
266    signal: &'static str,
267    label: &'static str,
268    route_kind: &'static str,
269    item_kind: &'static str,
270    count: usize,
271    unsafe_count: usize,
272    command: &'static str,
273    unsafe_command: Option<&'static str>,
274}