Skip to main content

fallow_api/
runtime_output.rs

1//! Typed programmatic runtime outputs and shared output-contract serializers.
2
3use std::path::{Path, PathBuf};
4
5use fallow_output::{
6    CheckOutput, DupesOutput, FeatureFlagFinding, FeatureFlagsOutput as FeatureFlagsOutputContract,
7    GroupByMode, HealthGroup, HealthGrouping, HealthJsonOutputInput, HealthOutputInput,
8    HealthReport, RootEnvelopeMode, health_meta,
9};
10use fallow_types::output::NextStep;
11use fallow_types::output_dead_code::{
12    BoundaryCallViolationFinding, BoundaryCoverageViolationFinding, BoundaryViolationFinding,
13    CircularDependencyFinding,
14};
15use fallow_types::results::AnalysisResults;
16use fallow_types::workspace::WorkspaceDiagnostic;
17use rustc_hash::FxHashSet;
18
19use crate::{AuditAttribution, AuditSummary, AuditVerdict};
20use crate::{CloneFamilyFinding, CloneGroupFinding, DupesReportPayload, DuplicationGroup};
21
22pub const HEALTH_SCHEMA_VERSION: u32 = 7;
23
24/// Concrete dead-code output contract returned by typed programmatic runs.
25pub type DeadCodeOutput = CheckOutput;
26
27/// Concrete circular-dependency output contract returned by typed runs.
28pub type CircularDependenciesOutput = CheckOutput;
29
30/// Concrete boundary-family output contract returned by typed runs.
31pub type BoundaryViolationsOutput = CheckOutput;
32
33/// Concrete duplication output contract returned by typed programmatic runs.
34pub type DuplicationOutput = DupesOutput<DupesReportPayload, DuplicationGroup>;
35
36/// Concrete feature-flag output contract returned by typed programmatic runs.
37pub type FeatureFlagsOutput = FeatureFlagsOutputContract;
38
39/// Concrete export trace output returned by typed programmatic runs.
40pub type TraceExportOutput = fallow_types::trace::ExportTrace;
41
42/// Concrete class / enum / store member trace output (the `trace_export`
43/// fallback when the name is a member rather than a top-level export). See
44/// issue #1744.
45pub type TraceClassMemberOutput = fallow_types::trace::ClassMemberTrace;
46
47/// The `trace_export` target: either a top-level export or (fallback) a class /
48/// enum / store member declared on one. Serialized untagged so the export shape
49/// stays byte-identical to the historical contract and the member shape matches
50/// the CLI's member trace; consumers distinguish by the presence of
51/// `export_name` (export) vs `member_name` / `owner_export` (member).
52#[derive(Debug, serde::Serialize)]
53#[serde(untagged)]
54pub enum TraceExportTargetOutput {
55    /// A top-level export trace.
56    Export(TraceExportOutput),
57    /// A class / enum / store member trace.
58    Member(TraceClassMemberOutput),
59}
60
61/// Concrete file trace output returned by typed programmatic runs.
62pub type TraceFileOutput = fallow_types::trace::FileTrace;
63
64/// Concrete dependency trace output returned by typed programmatic runs.
65pub type TraceDependencyOutput = fallow_types::trace::DependencyTrace;
66
67/// Concrete duplicate-code trace output returned by typed programmatic runs.
68pub type TraceCloneOutput = fallow_types::trace::CloneTrace;
69
70/// Inputs for serializing health JSON output through the API boundary.
71pub struct HealthJsonReportInput<'a> {
72    pub report: HealthReport,
73    pub root: &'a Path,
74    pub elapsed: std::time::Duration,
75    pub explain: bool,
76    pub grouped_by: Option<GroupByMode>,
77    pub groups: Option<Vec<HealthGroup>>,
78    pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
79    pub next_steps: Vec<NextStep>,
80    pub envelope_mode: RootEnvelopeMode,
81    pub telemetry_analysis_run_id: Option<&'a str>,
82}
83
84/// Typed programmatic combined output before JSON serialization.
85#[derive(Debug, Clone)]
86pub struct CombinedProgrammaticOutput {
87    pub dead_code: Option<DeadCodeProgrammaticOutput>,
88    pub duplication: Option<DuplicationProgrammaticOutput>,
89    pub health: Option<HealthProgrammaticOutput>,
90    pub root: PathBuf,
91    pub elapsed: std::time::Duration,
92    pub explain: bool,
93    pub next_steps: Vec<NextStep>,
94    pub envelope_mode: RootEnvelopeMode,
95    pub telemetry_analysis_run_id: Option<String>,
96}
97
98/// Typed programmatic dead-code output before JSON serialization.
99///
100/// This is the API boundary embedders should use when they need access to the
101/// typed engine/output result. Protocol surfaces serialize it explicitly at
102/// their JSON boundary.
103#[derive(Debug, Clone)]
104pub struct DeadCodeProgrammaticOutput {
105    pub output: DeadCodeOutput,
106    pub root: PathBuf,
107    pub config_fixable: bool,
108    pub envelope_mode: RootEnvelopeMode,
109    pub telemetry_analysis_run_id: Option<String>,
110}
111
112impl DeadCodeProgrammaticOutput {
113    /// Full typed dead-code issue arrays retained by this run.
114    #[must_use]
115    pub fn results(&self) -> &AnalysisResults {
116        &self.output.results
117    }
118
119    /// Project-relative root used when serializing stable JSON paths.
120    #[must_use]
121    pub fn root(&self) -> &Path {
122        &self.root
123    }
124}
125
126/// Typed programmatic circular-dependency output before JSON serialization.
127///
128/// The wire envelope stays the dead-code/check contract, but the Rust API
129/// surface is family-specific so embedders do not have to treat this as a
130/// generic dead-code run.
131#[derive(Debug, Clone)]
132pub struct CircularDependenciesProgrammaticOutput {
133    pub output: CircularDependenciesOutput,
134    pub root: PathBuf,
135    pub envelope_mode: RootEnvelopeMode,
136    pub telemetry_analysis_run_id: Option<String>,
137}
138
139impl CircularDependenciesProgrammaticOutput {
140    /// Full typed issue arrays retained by this family run.
141    #[must_use]
142    pub fn results(&self) -> &AnalysisResults {
143        &self.output.results
144    }
145
146    /// The circular dependency findings retained by this family run.
147    #[must_use]
148    pub fn circular_dependencies(&self) -> &[CircularDependencyFinding] {
149        &self.output.results.circular_dependencies
150    }
151}
152
153impl From<DeadCodeProgrammaticOutput> for CircularDependenciesProgrammaticOutput {
154    fn from(value: DeadCodeProgrammaticOutput) -> Self {
155        Self {
156            output: value.output,
157            root: value.root,
158            envelope_mode: value.envelope_mode,
159            telemetry_analysis_run_id: value.telemetry_analysis_run_id,
160        }
161    }
162}
163
164/// Typed programmatic boundary-family output before JSON serialization.
165///
166/// This covers banned imports, boundary coverage, and forbidden call findings
167/// while preserving the stable dead-code/check JSON envelope.
168#[derive(Debug, Clone)]
169pub struct BoundaryViolationsProgrammaticOutput {
170    pub output: BoundaryViolationsOutput,
171    pub root: PathBuf,
172    pub envelope_mode: RootEnvelopeMode,
173    pub telemetry_analysis_run_id: Option<String>,
174}
175
176impl BoundaryViolationsProgrammaticOutput {
177    /// Full typed issue arrays retained by this family run.
178    #[must_use]
179    pub fn results(&self) -> &AnalysisResults {
180        &self.output.results
181    }
182
183    /// Banned import boundary findings retained by this family run.
184    #[must_use]
185    pub fn boundary_violations(&self) -> &[BoundaryViolationFinding] {
186        &self.output.results.boundary_violations
187    }
188
189    /// Boundary coverage findings retained by this family run.
190    #[must_use]
191    pub fn boundary_coverage_violations(&self) -> &[BoundaryCoverageViolationFinding] {
192        &self.output.results.boundary_coverage_violations
193    }
194
195    /// Forbidden call findings retained by this family run.
196    #[must_use]
197    pub fn boundary_call_violations(&self) -> &[BoundaryCallViolationFinding] {
198        &self.output.results.boundary_call_violations
199    }
200}
201
202impl From<DeadCodeProgrammaticOutput> for BoundaryViolationsProgrammaticOutput {
203    fn from(value: DeadCodeProgrammaticOutput) -> Self {
204        Self {
205            output: value.output,
206            root: value.root,
207            envelope_mode: value.envelope_mode,
208            telemetry_analysis_run_id: value.telemetry_analysis_run_id,
209        }
210    }
211}
212
213/// Typed programmatic duplication output before JSON serialization.
214#[derive(Debug, Clone)]
215pub struct DuplicationProgrammaticOutput {
216    pub output: DuplicationOutput,
217    pub root: PathBuf,
218    pub threshold: f64,
219    pub envelope_mode: RootEnvelopeMode,
220    pub telemetry_analysis_run_id: Option<String>,
221}
222
223impl DuplicationProgrammaticOutput {
224    /// Typed duplication report payload retained by this run.
225    #[must_use]
226    pub const fn report(&self) -> &DupesReportPayload {
227        &self.output.report
228    }
229
230    /// Clone groups retained by this run, with typed actions and fingerprints.
231    #[must_use]
232    pub fn clone_groups(&self) -> &[CloneGroupFinding] {
233        &self.output.report.clone_groups
234    }
235
236    /// Clone families retained by this run, with nested typed clone groups.
237    #[must_use]
238    pub fn clone_families(&self) -> &[CloneFamilyFinding] {
239        &self.output.report.clone_families
240    }
241
242    /// Grouped duplication buckets when a grouping mode was used.
243    #[must_use]
244    pub fn groups(&self) -> Option<&[DuplicationGroup]> {
245        self.output.groups.as_deref()
246    }
247}
248
249/// Typed programmatic feature-flag output before JSON serialization.
250#[derive(Debug, Clone)]
251pub struct FeatureFlagsProgrammaticOutput {
252    pub output: FeatureFlagsOutput,
253    pub envelope_mode: RootEnvelopeMode,
254    pub telemetry_analysis_run_id: Option<String>,
255}
256
257impl FeatureFlagsProgrammaticOutput {
258    /// Feature flag findings retained by this run.
259    #[must_use]
260    pub fn feature_flags(&self) -> &[FeatureFlagFinding] {
261        &self.output.feature_flags
262    }
263
264    /// Number of feature flags retained by this run after scoping and limits.
265    #[must_use]
266    pub const fn total_flags(&self) -> usize {
267        self.output.total_flags
268    }
269}
270
271/// Typed programmatic export-trace output before JSON serialization. Carries
272/// either an export trace or (fallback) a class / enum / store member trace.
273#[derive(Debug)]
274pub struct TraceExportProgrammaticOutput {
275    pub output: TraceExportTargetOutput,
276}
277
278impl TraceExportProgrammaticOutput {
279    /// Typed export-or-member trace retained by this run.
280    #[must_use]
281    pub const fn trace(&self) -> &TraceExportTargetOutput {
282        &self.output
283    }
284
285    /// The export trace, when the target resolved to a top-level export.
286    #[must_use]
287    pub const fn as_export(&self) -> Option<&TraceExportOutput> {
288        match &self.output {
289            TraceExportTargetOutput::Export(export) => Some(export),
290            TraceExportTargetOutput::Member(_) => None,
291        }
292    }
293
294    /// The member trace, when the target resolved to a class / enum / store
295    /// member (the `trace_export` fallback, issue #1744).
296    #[must_use]
297    pub const fn as_member(&self) -> Option<&TraceClassMemberOutput> {
298        match &self.output {
299            TraceExportTargetOutput::Member(member) => Some(member),
300            TraceExportTargetOutput::Export(_) => None,
301        }
302    }
303}
304
305/// Typed programmatic file-trace output before JSON serialization.
306#[derive(Debug)]
307pub struct TraceFileProgrammaticOutput {
308    pub output: TraceFileOutput,
309}
310
311impl TraceFileProgrammaticOutput {
312    /// Typed file trace retained by this run.
313    #[must_use]
314    pub const fn trace(&self) -> &TraceFileOutput {
315        &self.output
316    }
317}
318
319/// Typed programmatic dependency-trace output before JSON serialization.
320#[derive(Debug)]
321pub struct TraceDependencyProgrammaticOutput {
322    pub output: TraceDependencyOutput,
323}
324
325impl TraceDependencyProgrammaticOutput {
326    /// Typed dependency trace retained by this run.
327    #[must_use]
328    pub const fn trace(&self) -> &TraceDependencyOutput {
329        &self.output
330    }
331}
332
333/// Typed programmatic duplicate-code trace output before JSON serialization.
334#[derive(Debug)]
335pub struct TraceCloneProgrammaticOutput {
336    pub output: TraceCloneOutput,
337}
338
339impl TraceCloneProgrammaticOutput {
340    /// Typed clone trace retained by this run.
341    #[must_use]
342    pub const fn trace(&self) -> &TraceCloneOutput {
343        &self.output
344    }
345}
346
347/// Typed programmatic health / complexity output before JSON serialization.
348#[derive(Debug, Clone)]
349pub struct HealthProgrammaticOutput {
350    pub report: HealthReport,
351    pub grouping: Option<HealthGrouping>,
352    pub root: PathBuf,
353    pub elapsed: std::time::Duration,
354    pub explain: bool,
355    pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
356    pub next_steps: Vec<NextStep>,
357    pub envelope_mode: RootEnvelopeMode,
358    pub telemetry_analysis_run_id: Option<String>,
359}
360
361/// Typed programmatic audit output before JSON serialization.
362#[derive(Debug, Clone)]
363pub struct AuditProgrammaticOutput {
364    pub verdict: AuditVerdict,
365    pub summary: AuditSummary,
366    pub attribution: AuditAttribution,
367    pub changed_files_count: usize,
368    pub base_ref: String,
369    pub base_description: Option<String>,
370    pub head_sha: Option<String>,
371    pub elapsed: std::time::Duration,
372    pub base_snapshot_skipped: Option<bool>,
373    pub base_snapshot: Option<AuditProgrammaticKeySnapshot>,
374    pub dead_code: Option<DeadCodeProgrammaticOutput>,
375    pub duplication: Option<DuplicationProgrammaticOutput>,
376    pub complexity: Option<HealthProgrammaticOutput>,
377    pub next_steps: Vec<NextStep>,
378    pub envelope_mode: RootEnvelopeMode,
379    pub telemetry_analysis_run_id: Option<String>,
380}
381
382/// Stable audit key snapshot used to classify introduced vs inherited findings.
383#[derive(Debug, Clone, Default)]
384pub struct AuditProgrammaticKeySnapshot {
385    pub dead_code: FxHashSet<String>,
386    pub health: FxHashSet<String>,
387    pub dupes: FxHashSet<String>,
388}
389
390/// Typed programmatic decision-surface output before JSON serialization.
391#[derive(Debug, Clone)]
392pub struct DecisionSurfaceProgrammaticOutput {
393    pub surface: fallow_output::DecisionSurface,
394    pub elapsed: std::time::Duration,
395    pub envelope_mode: RootEnvelopeMode,
396    pub telemetry_analysis_run_id: Option<String>,
397}
398
399/// Serialize a health / complexity report into the stable JSON output contract.
400///
401/// # Errors
402///
403/// Returns a serde error when the report cannot be converted to JSON.
404pub fn serialize_health_report_json(
405    input: HealthJsonReportInput<'_>,
406) -> Result<serde_json::Value, serde_json::Error> {
407    let root_prefix = format!("{}/", input.root.display());
408    fallow_output::serialize_health_json_output(HealthJsonOutputInput {
409        output: HealthOutputInput {
410            schema_version: HEALTH_SCHEMA_VERSION,
411            version: env!("CARGO_PKG_VERSION").to_string(),
412            elapsed: input.elapsed,
413            report: input.report,
414            grouped_by: input.grouped_by,
415            groups: input.groups,
416            meta: input.explain.then(health_meta),
417            workspace_diagnostics: input.workspace_diagnostics,
418            next_steps: input.next_steps,
419        },
420        root_prefix: Some(&root_prefix),
421        envelope_mode: input.envelope_mode,
422        analysis_run_id: input.telemetry_analysis_run_id,
423    })
424}