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 workspace_diagnostics: Vec<WorkspaceDiagnostic>,
78 pub next_steps: Vec<NextStep>,
79 pub envelope_mode: RootEnvelopeMode,
80 pub telemetry_analysis_run_id: Option<&'a str>,
81}
82
83pub struct DuplicationJsonOutputInput<'a> {
85 pub report: &'a DuplicationReport,
86 pub root: &'a Path,
87 pub elapsed: Duration,
88 pub meta: Option<Meta>,
89 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
90 pub next_steps: Vec<NextStep>,
91 pub envelope_mode: RootEnvelopeMode,
92 pub telemetry_analysis_run_id: Option<&'a str>,
93}
94
95pub struct GroupedDuplicationJsonOutputInput<'a> {
97 pub report: &'a DuplicationReport,
98 pub grouping: &'a DuplicationGrouping,
99 pub root: &'a Path,
100 pub elapsed: Duration,
101 pub meta: Option<Meta>,
102 pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
103 pub next_steps: Vec<NextStep>,
104 pub envelope_mode: RootEnvelopeMode,
105 pub telemetry_analysis_run_id: Option<&'a str>,
106}
107
108pub fn serialize_check_json(
114 input: CheckJsonOutputInput<'_>,
115) -> Result<serde_json::Value, serde_json::Error> {
116 let envelope = build_check_json_envelope(CheckJsonEnvelopeInput {
117 results: input.results,
118 elapsed: input.elapsed,
119 config_fixable: input.config_fixable,
120 meta: input.meta,
121 extras: input.extras,
122 workspace_diagnostics: input.workspace_diagnostics,
123 next_steps: input.next_steps,
124 });
125 let mut output = fallow_output::serialize_check_json_output(
126 envelope,
127 input.envelope_mode,
128 input.telemetry_analysis_run_id,
129 )?;
130 strip_json_root_prefix(&mut output, input.root);
131 Ok(output)
132}
133
134pub fn serialize_check_json_payload(
140 input: CheckJsonPayloadInput<'_>,
141) -> Result<serde_json::Value, serde_json::Error> {
142 let envelope = build_check_json_envelope(CheckJsonEnvelopeInput {
143 results: input.results,
144 elapsed: input.elapsed,
145 config_fixable: input.config_fixable,
146 meta: None,
147 extras: input.extras,
148 workspace_diagnostics: input.workspace_diagnostics,
149 next_steps: Vec::new(),
150 });
151 let mut output = serde_json::to_value(envelope)?;
152 strip_json_root_prefix(&mut output, input.root);
153 Ok(output)
154}
155
156pub fn serialize_grouped_check_json(
162 input: GroupedCheckJsonOutputInput<'_>,
163) -> Result<serde_json::Value, serde_json::Error> {
164 let entries = input
165 .groups
166 .iter()
167 .map(|group| {
168 let mut results = group.results.clone();
169 apply_config_fixable_to_duplicate_exports(&mut results, input.config_fixable);
170 harmonize_typed_suppress_line_actions(&mut results);
171 CheckGroupedEntry {
172 key: group.key.clone(),
173 owners: group.owners.clone(),
174 total_issues: results.total_issues(),
175 results,
176 }
177 })
178 .collect();
179
180 let envelope = CheckGroupedOutput {
181 schema_version: SchemaVersion(CHECK_SCHEMA_VERSION),
182 version: ToolVersion(env!("CARGO_PKG_VERSION").to_string()),
183 elapsed_ms: ElapsedMs(input.elapsed.as_millis() as u64),
184 grouped_by: input.grouped_by,
185 total_issues: input.original.total_issues(),
186 groups: entries,
187 meta: input.meta,
188 workspace_diagnostics: input.workspace_diagnostics,
189 next_steps: input.next_steps,
190 };
191
192 let mut output = fallow_output::serialize_check_grouped_json_output(
193 envelope,
194 input.envelope_mode,
195 input.telemetry_analysis_run_id,
196 )?;
197 strip_json_root_prefix(&mut output, input.root);
198 Ok(output)
199}
200
201pub fn serialize_duplication_json(
207 input: DuplicationJsonOutputInput<'_>,
208) -> Result<serde_json::Value, serde_json::Error> {
209 let payload = DupesReportPayload::from_report(input.report);
210 let envelope: DupesOutput<DupesReportPayload, DuplicationGroup> =
211 build_dupes_output(DupesOutputInput {
212 schema_version: CHECK_SCHEMA_VERSION,
213 version: env!("CARGO_PKG_VERSION").to_string(),
214 elapsed: input.elapsed,
215 report: payload,
216 grouped_by: None,
217 total_issues: None,
218 groups: None,
219 meta: input.meta,
220 workspace_diagnostics: input.workspace_diagnostics,
221 next_steps: input.next_steps,
222 });
223 let mut output = fallow_output::serialize_dupes_json_output(
224 envelope,
225 input.envelope_mode,
226 input.telemetry_analysis_run_id,
227 )?;
228 let root_prefix = format!("{}/", input.root.display());
229 strip_root_prefix(&mut output, &root_prefix);
230 Ok(output)
231}
232
233pub fn serialize_grouped_duplication_json(
239 input: GroupedDuplicationJsonOutputInput<'_>,
240) -> Result<serde_json::Value, serde_json::Error> {
241 let root_prefix = format!("{}/", input.root.display());
242 let payload = DupesReportPayload::from_report(input.report);
243 let envelope: DupesOutput<DupesReportPayload, DuplicationGroup> =
244 build_dupes_output(DupesOutputInput {
245 schema_version: CHECK_SCHEMA_VERSION,
246 version: env!("CARGO_PKG_VERSION").to_string(),
247 elapsed: input.elapsed,
248 report: payload,
249 grouped_by: Some(group_by_mode_from_label(input.grouping.mode)),
250 total_issues: Some(input.report.clone_groups.len()),
251 groups: None,
252 meta: input.meta,
253 workspace_diagnostics: input.workspace_diagnostics,
254 next_steps: input.next_steps,
255 });
256 let mut output = fallow_output::serialize_dupes_json_output(
257 envelope,
258 input.envelope_mode,
259 input.telemetry_analysis_run_id,
260 )?;
261 strip_root_prefix(&mut output, &root_prefix);
262
263 let group_values = input
264 .grouping
265 .groups
266 .iter()
267 .map(|group| {
268 let mut value = serde_json::to_value(group)?;
269 strip_root_prefix(&mut value, &root_prefix);
270 Ok(value)
271 })
272 .collect::<Result<Vec<_>, serde_json::Error>>()?;
273
274 if let serde_json::Value::Object(ref mut map) = output {
275 map.insert("groups".to_string(), serde_json::Value::Array(group_values));
276 }
277
278 Ok(output)
279}
280
281fn build_check_json_envelope(input: CheckJsonEnvelopeInput<'_>) -> CheckOutput {
282 let mut output = build_check_output(CheckOutputInput {
283 schema_version: CHECK_SCHEMA_VERSION,
284 version: env!("CARGO_PKG_VERSION").to_string(),
285 elapsed: input.elapsed,
286 results: input.results.clone(),
287 config_fixable: input.config_fixable,
288 meta: input.meta,
289 workspace_diagnostics: input.workspace_diagnostics,
290 next_steps: input.next_steps,
291 });
292 output.baseline_deltas = input.extras.baseline_deltas;
293 output.baseline = input.extras.baseline;
294 output.regression = input.extras.regression;
295 output
296}
297
298fn strip_json_root_prefix(output: &mut serde_json::Value, root: &Path) {
299 let root_prefix = format!("{}/", root.display());
300 strip_root_prefix(output, &root_prefix);
301}
302
303fn group_by_mode_from_label(label: &str) -> GroupByMode {
304 match label {
305 "directory" => GroupByMode::Directory,
306 "package" => GroupByMode::Package,
307 "section" => GroupByMode::Section,
308 _ => GroupByMode::Owner,
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use fallow_types::workspace::WorkspaceDiagnosticKind;
316
317 #[test]
318 fn grouped_check_json_carries_workspace_diagnostics_with_relative_paths() {
319 let root = Path::new("/project");
320 let output = serialize_grouped_check_json(GroupedCheckJsonOutputInput {
321 groups: &[],
322 original: &AnalysisResults::default(),
323 root,
324 elapsed: Duration::ZERO,
325 grouped_by: GroupByMode::Directory,
326 config_fixable: false,
327 meta: None,
328 workspace_diagnostics: vec![WorkspaceDiagnostic::new(
329 root,
330 root.join("src/unreadable.ts"),
331 WorkspaceDiagnosticKind::SourceReadFailure {
332 error: "permission denied".to_string(),
333 },
334 )],
335 next_steps: Vec::new(),
336 envelope_mode: RootEnvelopeMode::Tagged,
337 telemetry_analysis_run_id: None,
338 })
339 .expect("grouped check JSON serializes");
340
341 assert_eq!(
342 output["workspace_diagnostics"][0]["path"],
343 "src/unreadable.ts"
344 );
345 assert_eq!(
346 output["workspace_diagnostics"][0]["kind"],
347 "source-read-failure"
348 );
349 }
350}