Skip to main content

allow_report/
worklist_json.rs

1use crate::contracts::WORKLIST_ARTIFACT;
2use crate::json::{bool_json, json_string_array, option_json, push_json_fixed_artifact_preamble};
3use crate::worklist_summary::{
4    worklist_difficulty_count, worklist_kind_counts, worklist_risk_count,
5};
6use crate::{InventoryContext, WorklistFilters, WorklistItem};
7use allow_core::json_escape;
8
9pub fn render_worklist_json(
10    items: &[WorklistItem<'_>],
11    filters: WorklistFilters<'_>,
12    inventory: InventoryContext<'_>,
13) -> String {
14    let mut out = String::new();
15    out.push_str("{\n");
16    push_json_fixed_artifact_preamble(&mut out, WORKLIST_ARTIFACT, inventory);
17    out.push_str("  \"filters\": ");
18    out.push_str(&render_worklist_filters_json(filters, "  "));
19    out.push_str(",\n");
20    out.push_str("  \"summary\": {\n");
21    out.push_str(&format!("    \"work_items\": {},\n", items.len()));
22    out.push_str(&format!(
23        "    \"high\": {},\n",
24        worklist_risk_count(items, "high")
25    ));
26    out.push_str(&format!(
27        "    \"medium\": {},\n",
28        worklist_risk_count(items, "medium")
29    ));
30    out.push_str(&format!(
31        "    \"low\": {},\n",
32        worklist_risk_count(items, "low")
33    ));
34    out.push_str(&format!(
35        "    \"small_difficulty\": {},\n",
36        worklist_difficulty_count(items, "small")
37    ));
38    out.push_str(&format!(
39        "    \"medium_difficulty\": {}",
40        worklist_difficulty_count(items, "medium")
41    ));
42    let kind_counts = worklist_kind_counts(items);
43    if kind_counts.is_empty() {
44        out.push('\n');
45    } else {
46        out.push_str(",\n");
47        out.push_str("    \"item_kinds\": {\n");
48        for (index, (kind, count)) in kind_counts.iter().enumerate() {
49            if index > 0 {
50                out.push_str(",\n");
51            }
52            out.push_str(&format!("      \"{}\": {}", json_escape(kind), count));
53        }
54        out.push_str("\n    }\n");
55    }
56    out.push_str("  },\n");
57    out.push_str("  \"work_items\": [\n");
58    for (index, item) in items.iter().enumerate() {
59        if index > 0 {
60            out.push_str(",\n");
61        }
62        out.push_str(&render_work_item_json(item));
63    }
64    out.push_str("\n  ]\n");
65    out.push_str("}\n");
66    out
67}
68
69fn render_work_item_json(item: &WorklistItem<'_>) -> String {
70    let mut out = String::new();
71    out.push_str("    {\n");
72    out.push_str(&format!("      \"id\": \"{}\",\n", json_escape(item.id)));
73    out.push_str(&format!(
74        "      \"kind\": \"{}\",\n",
75        json_escape(item.kind)
76    ));
77    out.push_str(&format!(
78        "      \"exception_kind\": {},\n",
79        option_json(item.exception_kind)
80    ));
81    out.push_str(&format!(
82        "      \"family\": {},\n",
83        option_json(item.family)
84    ));
85    out.push_str(&format!("      \"owner\": {},\n", option_json(item.owner)));
86    out.push_str(&format!(
87        "      \"classification\": {},\n",
88        option_json(item.classification)
89    ));
90    out.push_str(&format!(
91        "      \"reason\": {},\n",
92        option_json(item.reason)
93    ));
94    out.push_str(&format!(
95        "      \"created\": {},\n",
96        option_json(item.created)
97    ));
98    out.push_str(&format!(
99        "      \"review_after\": {},\n",
100        option_json(item.review_after)
101    ));
102    out.push_str(&format!(
103        "      \"expires\": {},\n",
104        option_json(item.expires)
105    ));
106    out.push_str(&format!(
107        "      \"evidence_count\": {},\n",
108        item.evidence_count
109            .map(|count| count.to_string())
110            .unwrap_or_else(|| "null".to_string())
111    ));
112    if let Some(selector_precision) = item.selector_precision {
113        out.push_str(&format!(
114            "      \"selector_precision\": {selector_precision},\n"
115        ));
116    }
117    out.push_str(&format!(
118        "      \"risk\": \"{}\",\n",
119        json_escape(item.risk)
120    ));
121    out.push_str(&format!(
122        "      \"difficulty\": \"{}\",\n",
123        json_escape(item.difficulty)
124    ));
125    out.push_str(&format!(
126        "      \"status\": \"{}\",\n",
127        json_escape(item.status)
128    ));
129    out.push_str(&format!(
130        "      \"allow_id\": {},\n",
131        option_json(item.allow_id)
132    ));
133    out.push_str(&format!(
134        "      \"finding_index\": {},\n",
135        item.finding_index
136            .map(|index| index.to_string())
137            .unwrap_or_else(|| "null".to_string())
138    ));
139    out.push_str(&format!("      \"path\": {},\n", option_json(item.path)));
140    if let Some(reference) = item.evidence_reference.as_ref() {
141        out.push_str("      \"evidence_reference\": ");
142        out.push_str(&render_worklist_evidence_reference_json(reference));
143        out.push_str(",\n");
144    }
145    out.push_str(&format!(
146        "      \"source_package\": {},\n",
147        option_json(item.source_package)
148    ));
149    out.push_str(&format!(
150        "      \"message\": \"{}\",\n",
151        json_escape(item.message)
152    ));
153    out.push_str(&format!(
154        "      \"suggested_actions\": {},\n",
155        json_string_array(item.suggested_actions)
156    ));
157    out.push_str(&format!(
158        "      \"proof_commands\": {}\n",
159        json_string_array(item.proof_commands)
160    ));
161    out.push_str("    }");
162    out
163}
164
165fn render_worklist_evidence_reference_json(reference: &crate::EvidenceReference<'_>) -> String {
166    format!(
167        "{{\n        \"raw\": \"{}\",\n        \"prefix\": {},\n        \"target\": {},\n        \"status\": \"{}\",\n        \"category\": \"{}\",\n        \"message\": \"{}\"\n      }}",
168        json_escape(reference.raw),
169        option_json(reference.prefix),
170        option_json(reference.target),
171        json_escape(reference.status),
172        json_escape(reference.category),
173        json_escape(reference.message)
174    )
175}
176
177fn render_worklist_filters_json(filters: WorklistFilters<'_>, indent: &str) -> String {
178    let mut out = String::new();
179    out.push_str("{\n");
180    out.push_str(&format!(
181        "{indent}  \"kind\": {},\n",
182        option_json(filters.kind)
183    ));
184    out.push_str(&format!(
185        "{indent}  \"family\": {},\n",
186        option_json(filters.family)
187    ));
188    out.push_str(&format!(
189        "{indent}  \"item_kind\": {},\n",
190        option_json(filters.item_kind)
191    ));
192    out.push_str(&format!(
193        "{indent}  \"status\": {},\n",
194        option_json(filters.status)
195    ));
196    out.push_str(&format!(
197        "{indent}  \"allow_id\": {},\n",
198        option_json(filters.allow_id)
199    ));
200    out.push_str(&format!(
201        "{indent}  \"path\": {},\n",
202        option_json(filters.path)
203    ));
204    out.push_str(&format!(
205        "{indent}  \"source_package\": {},\n",
206        option_json(filters.source_package)
207    ));
208    out.push_str(&format!(
209        "{indent}  \"owner\": {},\n",
210        option_json(filters.owner)
211    ));
212    out.push_str(&format!(
213        "{indent}  \"classification\": {},\n",
214        option_json(filters.classification)
215    ));
216    out.push_str(&format!(
217        "{indent}  \"baseline_debt\": {},\n",
218        bool_json(filters.baseline_debt)
219    ));
220    out.push_str(&format!(
221        "{indent}  \"broad_scope\": {},\n",
222        bool_json(filters.broad_scope)
223    ));
224    out.push_str(&format!(
225        "{indent}  \"risk\": {},\n",
226        option_json(filters.risk)
227    ));
228    out.push_str(&format!(
229        "{indent}  \"difficulty\": {},\n",
230        option_json(filters.difficulty)
231    ));
232    out.push_str(&format!(
233        "{indent}  \"missing_evidence\": {}\n",
234        bool_json(filters.missing_evidence)
235    ));
236    out.push_str(&format!("{indent}}}"));
237    out
238}