1use std::path::Path;
4use std::time::Duration;
5
6use fallow_output::{
7 CHECK_SCHEMA_VERSION, CheckGroupedEntry, CheckGroupedOutput, CheckOutput, CheckOutputInput,
8 DupesOutput, DupesOutputInput, GroupByMode, RootEnvelopeMode,
9 apply_config_fixable_to_duplicate_exports, build_check_output, build_dupes_output,
10 harmonize_multi_kind_suppress_line_actions as harmonize_typed_suppress_line_actions,
11 strip_root_prefix,
12};
13use fallow_types::duplicates::DuplicationReport;
14use fallow_types::envelope::{
15 BaselineDeltas, BaselineMatch, ElapsedMs, Meta, RegressionResult, SchemaVersion, ToolVersion,
16};
17use fallow_types::output::NextStep;
18use fallow_types::results::AnalysisResults;
19use fallow_types::workspace::WorkspaceDiagnostic;
20
21use crate::{DupesReportPayload, DuplicationGroup, DuplicationGrouping, ResultGroup};
22
23pub struct CheckJsonOutputInput<'a> {
25 pub results: &'a AnalysisResults,
26 pub root: &'a Path,
27 pub elapsed: Duration,
28 pub config_fixable: bool,
29 pub meta: Option<Meta>,
30 pub extras: CheckJsonExtraOutputs,
31 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
32 pub next_steps: Vec<NextStep>,
33 pub envelope_mode: RootEnvelopeMode,
34 pub telemetry_analysis_run_id: Option<&'a str>,
35}
36
37pub struct CheckJsonPayloadInput<'a> {
39 pub results: &'a AnalysisResults,
40 pub root: &'a Path,
41 pub elapsed: Duration,
42 pub config_fixable: bool,
43 pub extras: CheckJsonExtraOutputs,
44 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
45}
46
47#[derive(Debug, Clone, Default)]
52pub struct CheckJsonExtraOutputs {
53 pub baseline_deltas: Option<BaselineDeltas>,
54 pub baseline: Option<BaselineMatch>,
55 pub regression: Option<RegressionResult>,
56}
57
58struct CheckJsonEnvelopeInput<'a> {
59 results: &'a AnalysisResults,
60 elapsed: Duration,
61 config_fixable: bool,
62 meta: Option<Meta>,
63 extras: CheckJsonExtraOutputs,
64 workspace_diagnostics: Vec<WorkspaceDiagnostic>,
65 next_steps: Vec<NextStep>,
66}
67
68pub struct GroupedCheckJsonOutputInput<'a> {
70 pub groups: &'a [ResultGroup],
71 pub original: &'a AnalysisResults,
72 pub root: &'a Path,
73 pub elapsed: Duration,
74 pub grouped_by: GroupByMode,
75 pub config_fixable: bool,
76 pub meta: Option<Meta>,
77 pub next_steps: Vec<NextStep>,
78 pub envelope_mode: RootEnvelopeMode,
79 pub telemetry_analysis_run_id: Option<&'a str>,
80}
81
82pub struct DuplicationJsonOutputInput<'a> {
84 pub report: &'a DuplicationReport,
85 pub root: &'a Path,
86 pub elapsed: Duration,
87 pub meta: Option<Meta>,
88 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
89 pub next_steps: Vec<NextStep>,
90 pub envelope_mode: RootEnvelopeMode,
91 pub telemetry_analysis_run_id: Option<&'a str>,
92}
93
94pub struct GroupedDuplicationJsonOutputInput<'a> {
96 pub report: &'a DuplicationReport,
97 pub grouping: &'a DuplicationGrouping,
98 pub root: &'a Path,
99 pub elapsed: Duration,
100 pub meta: Option<Meta>,
101 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
102 pub next_steps: Vec<NextStep>,
103 pub envelope_mode: RootEnvelopeMode,
104 pub telemetry_analysis_run_id: Option<&'a str>,
105}
106
107pub fn serialize_check_json(
113 input: CheckJsonOutputInput<'_>,
114) -> Result<serde_json::Value, serde_json::Error> {
115 let envelope = build_check_json_envelope(CheckJsonEnvelopeInput {
116 results: input.results,
117 elapsed: input.elapsed,
118 config_fixable: input.config_fixable,
119 meta: input.meta,
120 extras: input.extras,
121 workspace_diagnostics: input.workspace_diagnostics,
122 next_steps: input.next_steps,
123 });
124 let mut output = fallow_output::serialize_check_json_output(
125 envelope,
126 input.envelope_mode,
127 input.telemetry_analysis_run_id,
128 )?;
129 strip_json_root_prefix(&mut output, input.root);
130 Ok(output)
131}
132
133pub fn serialize_check_json_payload(
139 input: CheckJsonPayloadInput<'_>,
140) -> Result<serde_json::Value, serde_json::Error> {
141 let envelope = build_check_json_envelope(CheckJsonEnvelopeInput {
142 results: input.results,
143 elapsed: input.elapsed,
144 config_fixable: input.config_fixable,
145 meta: None,
146 extras: input.extras,
147 workspace_diagnostics: input.workspace_diagnostics,
148 next_steps: Vec::new(),
149 });
150 let mut output = serde_json::to_value(envelope)?;
151 strip_json_root_prefix(&mut output, input.root);
152 Ok(output)
153}
154
155pub fn serialize_grouped_check_json(
161 input: GroupedCheckJsonOutputInput<'_>,
162) -> Result<serde_json::Value, serde_json::Error> {
163 let entries = input
164 .groups
165 .iter()
166 .map(|group| {
167 let mut results = group.results.clone();
168 apply_config_fixable_to_duplicate_exports(&mut results, input.config_fixable);
169 harmonize_typed_suppress_line_actions(&mut results);
170 CheckGroupedEntry {
171 key: group.key.clone(),
172 owners: group.owners.clone(),
173 total_issues: results.total_issues(),
174 results,
175 }
176 })
177 .collect();
178
179 let envelope = CheckGroupedOutput {
180 schema_version: SchemaVersion(CHECK_SCHEMA_VERSION),
181 version: ToolVersion(env!("CARGO_PKG_VERSION").to_string()),
182 elapsed_ms: ElapsedMs(input.elapsed.as_millis() as u64),
183 grouped_by: input.grouped_by,
184 total_issues: input.original.total_issues(),
185 groups: entries,
186 meta: input.meta,
187 next_steps: input.next_steps,
188 };
189
190 let mut output = fallow_output::serialize_check_grouped_json_output(
191 envelope,
192 input.envelope_mode,
193 input.telemetry_analysis_run_id,
194 )?;
195 let root_prefix = format!("{}/", input.root.display());
196 if let Some(arr) = output
197 .get_mut("groups")
198 .and_then(serde_json::Value::as_array_mut)
199 {
200 for entry in arr {
201 strip_root_prefix(entry, &root_prefix);
202 }
203 }
204 Ok(output)
205}
206
207pub fn serialize_duplication_json(
213 input: DuplicationJsonOutputInput<'_>,
214) -> Result<serde_json::Value, serde_json::Error> {
215 let payload = DupesReportPayload::from_report(input.report);
216 let envelope: DupesOutput<DupesReportPayload, DuplicationGroup> =
217 build_dupes_output(DupesOutputInput {
218 schema_version: CHECK_SCHEMA_VERSION,
219 version: env!("CARGO_PKG_VERSION").to_string(),
220 elapsed: input.elapsed,
221 report: payload,
222 grouped_by: None,
223 total_issues: None,
224 groups: None,
225 meta: input.meta,
226 workspace_diagnostics: input.workspace_diagnostics,
227 next_steps: input.next_steps,
228 });
229 let mut output = fallow_output::serialize_dupes_json_output(
230 envelope,
231 input.envelope_mode,
232 input.telemetry_analysis_run_id,
233 )?;
234 let root_prefix = format!("{}/", input.root.display());
235 strip_root_prefix(&mut output, &root_prefix);
236 Ok(output)
237}
238
239pub fn serialize_grouped_duplication_json(
245 input: GroupedDuplicationJsonOutputInput<'_>,
246) -> Result<serde_json::Value, serde_json::Error> {
247 let root_prefix = format!("{}/", input.root.display());
248 let payload = DupesReportPayload::from_report(input.report);
249 let envelope: DupesOutput<DupesReportPayload, DuplicationGroup> =
250 build_dupes_output(DupesOutputInput {
251 schema_version: CHECK_SCHEMA_VERSION,
252 version: env!("CARGO_PKG_VERSION").to_string(),
253 elapsed: input.elapsed,
254 report: payload,
255 grouped_by: Some(group_by_mode_from_label(input.grouping.mode)),
256 total_issues: Some(input.report.clone_groups.len()),
257 groups: None,
258 meta: input.meta,
259 workspace_diagnostics: input.workspace_diagnostics,
260 next_steps: input.next_steps,
261 });
262 let mut output = fallow_output::serialize_dupes_json_output(
263 envelope,
264 input.envelope_mode,
265 input.telemetry_analysis_run_id,
266 )?;
267 strip_root_prefix(&mut output, &root_prefix);
268
269 let group_values = input
270 .grouping
271 .groups
272 .iter()
273 .map(|group| {
274 let mut value = serde_json::to_value(group)?;
275 strip_root_prefix(&mut value, &root_prefix);
276 Ok(value)
277 })
278 .collect::<Result<Vec<_>, serde_json::Error>>()?;
279
280 if let serde_json::Value::Object(ref mut map) = output {
281 map.insert("groups".to_string(), serde_json::Value::Array(group_values));
282 }
283
284 Ok(output)
285}
286
287fn build_check_json_envelope(input: CheckJsonEnvelopeInput<'_>) -> CheckOutput {
288 let mut output = build_check_output(CheckOutputInput {
289 schema_version: CHECK_SCHEMA_VERSION,
290 version: env!("CARGO_PKG_VERSION").to_string(),
291 elapsed: input.elapsed,
292 results: input.results.clone(),
293 config_fixable: input.config_fixable,
294 meta: input.meta,
295 workspace_diagnostics: input.workspace_diagnostics,
296 next_steps: input.next_steps,
297 });
298 output.baseline_deltas = input.extras.baseline_deltas;
299 output.baseline = input.extras.baseline;
300 output.regression = input.extras.regression;
301 output
302}
303
304fn strip_json_root_prefix(output: &mut serde_json::Value, root: &Path) {
305 let root_prefix = format!("{}/", root.display());
306 strip_root_prefix(output, &root_prefix);
307}
308
309fn group_by_mode_from_label(label: &str) -> GroupByMode {
310 match label {
311 "directory" => GroupByMode::Directory,
312 "package" => GroupByMode::Package,
313 "section" => GroupByMode::Section,
314 _ => GroupByMode::Owner,
315 }
316}