Skip to main content

cargo_coupling/metrics/
dimensions.rs

1use std::fmt;
2use std::path::Path;
3
4use crate::volatility::Volatility;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7pub enum Visibility {
8    /// Fully public (`pub`)
9    Public,
10    /// Crate-internal (`pub(crate)`)
11    PubCrate,
12    /// Super-module visible (`pub(super)`)
13    PubSuper,
14    /// Module-path restricted (`pub(in path)`)
15    PubIn,
16    /// Private (no visibility modifier)
17    #[default]
18    Private,
19}
20
21impl Visibility {
22    /// Check if this visibility allows access from a different module
23    pub fn allows_external_access(&self) -> bool {
24        matches!(self, Visibility::Public | Visibility::PubCrate)
25    }
26
27    /// Check if access from another module would be "intrusive"
28    ///
29    /// Intrusive access means accessing something that isn't part of the public API.
30    /// This indicates tight coupling to implementation details.
31    pub fn is_intrusive_from(&self, same_crate: bool, same_module: bool) -> bool {
32        if same_module {
33            // Same module access is never intrusive
34            return false;
35        }
36
37        match self {
38            Visibility::Public => false,         // Public API, not intrusive
39            Visibility::PubCrate => !same_crate, // Intrusive if from different crate
40            Visibility::PubSuper | Visibility::PubIn => true, // Limited visibility, intrusive from outside
41            Visibility::Private => true, // Private, always intrusive from outside
42        }
43    }
44
45    /// Get a penalty multiplier for coupling strength based on visibility
46    ///
47    /// Higher penalty = more "intrusive" the access is.
48    pub fn intrusive_penalty(&self) -> f64 {
49        match self {
50            Visibility::Public => 0.0,    // No penalty for public API
51            Visibility::PubCrate => 0.25, // Small penalty for crate-internal
52            Visibility::PubSuper => 0.5,  // Medium penalty
53            Visibility::PubIn => 0.5,     // Medium penalty
54            Visibility::Private => 1.0,   // Full penalty for private access
55        }
56    }
57}
58
59impl fmt::Display for Visibility {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            Visibility::Public => write!(f, "pub"),
63            Visibility::PubCrate => write!(f, "pub(crate)"),
64            Visibility::PubSuper => write!(f, "pub(super)"),
65            Visibility::PubIn => write!(f, "pub(in ...)"),
66            Visibility::Private => write!(f, "private"),
67        }
68    }
69}
70
71/// Integration strength levels (how much knowledge is shared)
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub enum IntegrationStrength {
74    /// Strongest coupling - direct access to internals
75    Intrusive,
76    /// Strong coupling - depends on function signatures
77    Functional,
78    /// Medium coupling - depends on data models
79    Model,
80    /// Weakest coupling - depends only on contracts/traits
81    Contract,
82}
83
84impl IntegrationStrength {
85    /// Returns the numeric value (0.0 - 1.0, higher = stronger)
86    pub fn value(&self) -> f64 {
87        match self {
88            IntegrationStrength::Intrusive => 1.0,
89            IntegrationStrength::Functional => 0.75,
90            IntegrationStrength::Model => 0.5,
91            IntegrationStrength::Contract => 0.25,
92        }
93    }
94}
95
96/// Distance levels (how far apart components are)
97#[derive(Debug, Clone, Copy, PartialEq)]
98pub enum Distance {
99    /// Same function/block
100    SameFunction,
101    /// Same module or structurally adjacent (ancestor/descendant, or siblings under one parent package)
102    SameModule,
103    /// Different module in same crate
104    DifferentModule,
105    /// Different crate
106    DifferentCrate,
107}
108
109impl Distance {
110    /// Returns the numeric value (0.0 - 1.0, higher = farther)
111    pub fn value(&self) -> f64 {
112        match self {
113            Distance::SameFunction => 0.0,
114            Distance::SameModule => 0.25,
115            Distance::DifferentModule => 0.5,
116            Distance::DifferentCrate => 1.0,
117        }
118    }
119}
120
121/// DDD subdomain type
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum Subdomain {
124    /// Core subdomain - competitive advantage, high volatility
125    Core,
126    /// Supporting subdomain - stable business logic, low volatility
127    Supporting,
128    /// Generic subdomain - solved problems, stable implementations
129    Generic,
130}
131
132impl Subdomain {
133    /// Map subdomain to expected volatility level
134    pub fn expected_volatility(&self) -> Volatility {
135        match self {
136            Subdomain::Core => Volatility::High,
137            Subdomain::Supporting => Volatility::Low,
138            Subdomain::Generic => Volatility::Low,
139        }
140    }
141}
142
143impl fmt::Display for Subdomain {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        match self {
146            Subdomain::Core => write!(f, "Core"),
147            Subdomain::Supporting => write!(f, "Supporting"),
148            Subdomain::Generic => write!(f, "Generic"),
149        }
150    }
151}
152
153/// Minimal configuration surface needed by project metrics.
154pub trait MetricsConfig {
155    /// Directory containing the loaded config file, if any.
156    fn config_root(&self) -> Option<&Path>;
157    /// Whether path-based volatility overrides exist.
158    fn has_volatility_overrides(&self) -> bool;
159    /// Whether subdomain classification exists.
160    fn has_subdomain_config(&self) -> bool;
161    /// Resolve a path to its configured subdomain, if any.
162    fn get_subdomain(&self, path: &str) -> Option<Subdomain>;
163    /// Resolve a path to its explicit volatility override, if any.
164    fn get_volatility_override(&mut self, path: &str) -> Option<Volatility>;
165}
166
167// ===== Coupling Records =====