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}