crap_core/ports/mod.rs
1use crate::domain::types::{
2 BranchCoverage, ComplexityMetric, CrapError, FileChangeKind, FunctionComplexity, LineCoverage,
3};
4use serde::Serialize;
5use serde::de::DeserializeOwned;
6use std::collections::HashMap;
7use std::fmt::Debug;
8use std::path::Path;
9
10/// Port for extracting per-function complexity from source code.
11pub trait ComplexityPort {
12 fn extract(
13 &self,
14 source: &str,
15 file_path: &str,
16 metric: ComplexityMetric,
17 ) -> Result<Vec<FunctionComplexity>, CrapError>;
18}
19
20/// Trait implemented by adapter-specific parse diagnostic types.
21///
22/// `LcovParseDiagnostic` (in `crap4rs`) is the v0.5 LCOV implementation;
23/// future siblings (`crap4ts`'s Istanbul adapter) will define their own
24/// concrete type implementing this trait. Bounds are minimal but
25/// load-bearing: `Serialize + DeserializeOwned` lets `AnalysisDiagnostics<P>`
26/// and `ParseOutput<P>` keep their auto-derived serde shapes; `Debug + Clone`
27/// match the rest of the domain's pervasive derive set. The trait itself
28/// is not object-safe (`Clone` requires `Sized`); object safety is enforced
29/// one layer up on `&dyn CoveragePort<Diagnostic = …>` where the caller
30/// fixes the associated type. See ADR D9 (mixed-dispatch-strategy).
31pub trait ParseDiagnostic: Debug + Clone + Serialize + DeserializeOwned {}
32
33/// Result of parsing coverage data: coverage map + non-fatal diagnostics.
34///
35/// Generic over `P: ParseDiagnostic` so the LCOV adapter (`crap4rs`),
36/// the future Istanbul adapter (`crap4ts`), and any sibling adapter can
37/// thread their own diagnostic shape through one shared analyzer
38/// pipeline. Per ADR D9, `Vec<P>` storage of potentially thousands of
39/// parse diagnostics avoids the per-element `Box<dyn ParseDiagnostic>`
40/// heap-allocation cost.
41#[derive(Debug)]
42pub struct ParseOutput<P: ParseDiagnostic> {
43 pub coverage: HashMap<String, Vec<LineCoverage>>,
44 /// Branch coverage data from BRDA records, keyed by file path.
45 /// `None` when no BRDA records were encountered in the entire input.
46 pub branches: Option<HashMap<String, Vec<BranchCoverage>>>,
47 pub diagnostics: Vec<P>,
48}
49
50/// Port for parsing coverage data into per-file, per-line hit counts.
51///
52/// `Diagnostic` associated type lets concrete impls fix their parse
53/// diagnostic shape (`LcovParseDiagnostic` for the Rust adapter). The
54/// trait is dyn-compatible when callers fix the associated type at the
55/// dyn site: `&dyn CoveragePort<Diagnostic = LcovParseDiagnostic>`.
56/// See ADR D9 for the mixed-dispatch rationale.
57pub trait CoveragePort {
58 type Diagnostic: ParseDiagnostic;
59 fn parse(&self, data: &str) -> Result<ParseOutput<Self::Diagnostic>, CrapError>;
60}
61
62/// Port for computing which files/regions changed relative to a git ref.
63pub trait DiffPort {
64 fn changed_regions(
65 &self,
66 diff_ref: &str,
67 working_dir: &Path,
68 paths: &[String],
69 ) -> Result<HashMap<String, FileChangeKind>, CrapError>;
70}
71
72#[cfg(test)]
73mod object_safety {
74 //! Compile-fence: locks `&dyn CoveragePort<Diagnostic = ...>` and
75 //! `&dyn ComplexityPort` as object-safe, per ADR D9. If a future
76 //! port method becomes generic, returns `impl Trait`, or takes
77 //! `Self`, this test fails to compile and the dispatch contract
78 //! is the loud failure point.
79 use super::*;
80 use crate::test_strategies::DummyParseDiagnostic;
81
82 #[allow(dead_code)]
83 fn _coverage_port_is_dyn_safe(_port: &dyn CoveragePort<Diagnostic = DummyParseDiagnostic>) {}
84
85 #[allow(dead_code)]
86 fn _complexity_port_is_dyn_safe(_port: &dyn ComplexityPort) {}
87}