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