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 file trace output returned by typed programmatic runs.
43pub type TraceFileOutput = fallow_types::trace::FileTrace;
44
45/// Concrete dependency trace output returned by typed programmatic runs.
46pub type TraceDependencyOutput = fallow_types::trace::DependencyTrace;
47
48/// Concrete duplicate-code trace output returned by typed programmatic runs.
49pub type TraceCloneOutput = fallow_types::trace::CloneTrace;
50
51/// Inputs for serializing health JSON output through the API boundary.
52pub struct HealthJsonReportInput<'a> {
53    pub report: HealthReport,
54    pub root: &'a Path,
55    pub elapsed: std::time::Duration,
56    pub explain: bool,
57    pub grouped_by: Option<GroupByMode>,
58    pub groups: Option<Vec<HealthGroup>>,
59    pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
60    pub next_steps: Vec<NextStep>,
61    pub envelope_mode: RootEnvelopeMode,
62    pub telemetry_analysis_run_id: Option<&'a str>,
63}
64
65/// Typed programmatic dead-code output before JSON serialization.
66///
67/// This is the API boundary embedders should use when they need access to the
68/// typed engine/output result. Protocol surfaces serialize it explicitly at
69/// their JSON boundary.
70#[derive(Debug, Clone)]
71pub struct DeadCodeProgrammaticOutput {
72    pub output: DeadCodeOutput,
73    pub root: PathBuf,
74    pub envelope_mode: RootEnvelopeMode,
75    pub telemetry_analysis_run_id: Option<String>,
76}
77
78impl DeadCodeProgrammaticOutput {
79    /// Full typed dead-code issue arrays retained by this run.
80    #[must_use]
81    pub fn results(&self) -> &AnalysisResults {
82        &self.output.results
83    }
84
85    /// Project-relative root used when serializing stable JSON paths.
86    #[must_use]
87    pub fn root(&self) -> &Path {
88        &self.root
89    }
90}
91
92/// Typed programmatic circular-dependency output before JSON serialization.
93///
94/// The wire envelope stays the dead-code/check contract, but the Rust API
95/// surface is family-specific so embedders do not have to treat this as a
96/// generic dead-code run.
97#[derive(Debug, Clone)]
98pub struct CircularDependenciesProgrammaticOutput {
99    pub output: CircularDependenciesOutput,
100    pub root: PathBuf,
101    pub envelope_mode: RootEnvelopeMode,
102    pub telemetry_analysis_run_id: Option<String>,
103}
104
105impl CircularDependenciesProgrammaticOutput {
106    /// Full typed issue arrays retained by this family run.
107    #[must_use]
108    pub fn results(&self) -> &AnalysisResults {
109        &self.output.results
110    }
111
112    /// The circular dependency findings retained by this family run.
113    #[must_use]
114    pub fn circular_dependencies(&self) -> &[CircularDependencyFinding] {
115        &self.output.results.circular_dependencies
116    }
117}
118
119impl From<DeadCodeProgrammaticOutput> for CircularDependenciesProgrammaticOutput {
120    fn from(value: DeadCodeProgrammaticOutput) -> Self {
121        Self {
122            output: value.output,
123            root: value.root,
124            envelope_mode: value.envelope_mode,
125            telemetry_analysis_run_id: value.telemetry_analysis_run_id,
126        }
127    }
128}
129
130/// Typed programmatic boundary-family output before JSON serialization.
131///
132/// This covers banned imports, boundary coverage, and forbidden call findings
133/// while preserving the stable dead-code/check JSON envelope.
134#[derive(Debug, Clone)]
135pub struct BoundaryViolationsProgrammaticOutput {
136    pub output: BoundaryViolationsOutput,
137    pub root: PathBuf,
138    pub envelope_mode: RootEnvelopeMode,
139    pub telemetry_analysis_run_id: Option<String>,
140}
141
142impl BoundaryViolationsProgrammaticOutput {
143    /// Full typed issue arrays retained by this family run.
144    #[must_use]
145    pub fn results(&self) -> &AnalysisResults {
146        &self.output.results
147    }
148
149    /// Banned import boundary findings retained by this family run.
150    #[must_use]
151    pub fn boundary_violations(&self) -> &[BoundaryViolationFinding] {
152        &self.output.results.boundary_violations
153    }
154
155    /// Boundary coverage findings retained by this family run.
156    #[must_use]
157    pub fn boundary_coverage_violations(&self) -> &[BoundaryCoverageViolationFinding] {
158        &self.output.results.boundary_coverage_violations
159    }
160
161    /// Forbidden call findings retained by this family run.
162    #[must_use]
163    pub fn boundary_call_violations(&self) -> &[BoundaryCallViolationFinding] {
164        &self.output.results.boundary_call_violations
165    }
166}
167
168impl From<DeadCodeProgrammaticOutput> for BoundaryViolationsProgrammaticOutput {
169    fn from(value: DeadCodeProgrammaticOutput) -> Self {
170        Self {
171            output: value.output,
172            root: value.root,
173            envelope_mode: value.envelope_mode,
174            telemetry_analysis_run_id: value.telemetry_analysis_run_id,
175        }
176    }
177}
178
179/// Typed programmatic duplication output before JSON serialization.
180#[derive(Debug, Clone)]
181pub struct DuplicationProgrammaticOutput {
182    pub output: DuplicationOutput,
183    pub root: PathBuf,
184    pub threshold: f64,
185    pub envelope_mode: RootEnvelopeMode,
186    pub telemetry_analysis_run_id: Option<String>,
187}
188
189impl DuplicationProgrammaticOutput {
190    /// Typed duplication report payload retained by this run.
191    #[must_use]
192    pub const fn report(&self) -> &DupesReportPayload {
193        &self.output.report
194    }
195
196    /// Clone groups retained by this run, with typed actions and fingerprints.
197    #[must_use]
198    pub fn clone_groups(&self) -> &[CloneGroupFinding] {
199        &self.output.report.clone_groups
200    }
201
202    /// Clone families retained by this run, with nested typed clone groups.
203    #[must_use]
204    pub fn clone_families(&self) -> &[CloneFamilyFinding] {
205        &self.output.report.clone_families
206    }
207
208    /// Grouped duplication buckets when a grouping mode was used.
209    #[must_use]
210    pub fn groups(&self) -> Option<&[DuplicationGroup]> {
211        self.output.groups.as_deref()
212    }
213}
214
215/// Typed programmatic feature-flag output before JSON serialization.
216#[derive(Debug, Clone)]
217pub struct FeatureFlagsProgrammaticOutput {
218    pub output: FeatureFlagsOutput,
219    pub envelope_mode: RootEnvelopeMode,
220    pub telemetry_analysis_run_id: Option<String>,
221}
222
223impl FeatureFlagsProgrammaticOutput {
224    /// Feature flag findings retained by this run.
225    #[must_use]
226    pub fn feature_flags(&self) -> &[FeatureFlagFinding] {
227        &self.output.feature_flags
228    }
229
230    /// Number of feature flags retained by this run after scoping and limits.
231    #[must_use]
232    pub const fn total_flags(&self) -> usize {
233        self.output.total_flags
234    }
235}
236
237/// Typed programmatic export-trace output before JSON serialization.
238#[derive(Debug)]
239pub struct TraceExportProgrammaticOutput {
240    pub output: TraceExportOutput,
241}
242
243impl TraceExportProgrammaticOutput {
244    /// Typed export trace retained by this run.
245    #[must_use]
246    pub const fn trace(&self) -> &TraceExportOutput {
247        &self.output
248    }
249}
250
251/// Typed programmatic file-trace output before JSON serialization.
252#[derive(Debug)]
253pub struct TraceFileProgrammaticOutput {
254    pub output: TraceFileOutput,
255}
256
257impl TraceFileProgrammaticOutput {
258    /// Typed file trace retained by this run.
259    #[must_use]
260    pub const fn trace(&self) -> &TraceFileOutput {
261        &self.output
262    }
263}
264
265/// Typed programmatic dependency-trace output before JSON serialization.
266#[derive(Debug)]
267pub struct TraceDependencyProgrammaticOutput {
268    pub output: TraceDependencyOutput,
269}
270
271impl TraceDependencyProgrammaticOutput {
272    /// Typed dependency trace retained by this run.
273    #[must_use]
274    pub const fn trace(&self) -> &TraceDependencyOutput {
275        &self.output
276    }
277}
278
279/// Typed programmatic duplicate-code trace output before JSON serialization.
280#[derive(Debug)]
281pub struct TraceCloneProgrammaticOutput {
282    pub output: TraceCloneOutput,
283}
284
285impl TraceCloneProgrammaticOutput {
286    /// Typed clone trace retained by this run.
287    #[must_use]
288    pub const fn trace(&self) -> &TraceCloneOutput {
289        &self.output
290    }
291}
292
293/// Typed programmatic health / complexity output before JSON serialization.
294#[derive(Debug, Clone)]
295pub struct HealthProgrammaticOutput {
296    pub report: HealthReport,
297    pub grouping: Option<HealthGrouping>,
298    pub root: PathBuf,
299    pub elapsed: std::time::Duration,
300    pub explain: bool,
301    pub workspace_diagnostics: Vec<WorkspaceDiagnostic>,
302    pub next_steps: Vec<NextStep>,
303    pub envelope_mode: RootEnvelopeMode,
304    pub telemetry_analysis_run_id: Option<String>,
305}
306
307/// Typed programmatic audit output before JSON serialization.
308#[derive(Debug, Clone)]
309pub struct AuditProgrammaticOutput {
310    pub verdict: AuditVerdict,
311    pub summary: AuditSummary,
312    pub attribution: AuditAttribution,
313    pub changed_files_count: usize,
314    pub base_ref: String,
315    pub base_description: Option<String>,
316    pub head_sha: Option<String>,
317    pub elapsed: std::time::Duration,
318    pub base_snapshot_skipped: Option<bool>,
319    pub base_snapshot: Option<AuditProgrammaticKeySnapshot>,
320    pub dead_code: Option<DeadCodeProgrammaticOutput>,
321    pub duplication: Option<DuplicationProgrammaticOutput>,
322    pub complexity: Option<HealthProgrammaticOutput>,
323    pub next_steps: Vec<NextStep>,
324    pub envelope_mode: RootEnvelopeMode,
325    pub telemetry_analysis_run_id: Option<String>,
326}
327
328/// Stable audit key snapshot used to classify introduced vs inherited findings.
329#[derive(Debug, Clone, Default)]
330pub struct AuditProgrammaticKeySnapshot {
331    pub dead_code: FxHashSet<String>,
332    pub health: FxHashSet<String>,
333    pub dupes: FxHashSet<String>,
334}
335
336/// Typed programmatic decision-surface output before JSON serialization.
337#[derive(Debug, Clone)]
338pub struct DecisionSurfaceProgrammaticOutput {
339    pub surface: fallow_output::DecisionSurface,
340    pub elapsed: std::time::Duration,
341    pub envelope_mode: RootEnvelopeMode,
342    pub telemetry_analysis_run_id: Option<String>,
343}
344
345/// Serialize a health / complexity report into the stable JSON output contract.
346///
347/// # Errors
348///
349/// Returns a serde error when the report cannot be converted to JSON.
350pub fn serialize_health_report_json(
351    input: HealthJsonReportInput<'_>,
352) -> Result<serde_json::Value, serde_json::Error> {
353    let root_prefix = format!("{}/", input.root.display());
354    fallow_output::serialize_health_json_output(HealthJsonOutputInput {
355        output: HealthOutputInput {
356            schema_version: HEALTH_SCHEMA_VERSION,
357            version: env!("CARGO_PKG_VERSION").to_string(),
358            elapsed: input.elapsed,
359            report: input.report,
360            grouped_by: input.grouped_by,
361            groups: input.groups,
362            meta: input.explain.then(health_meta),
363            workspace_diagnostics: input.workspace_diagnostics,
364            next_steps: input.next_steps,
365        },
366        root_prefix: Some(&root_prefix),
367        envelope_mode: input.envelope_mode,
368        analysis_run_id: input.telemetry_analysis_run_id,
369    })
370}