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