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}