cargo_coupling/balance/
issue.rs1use super::action::RefactoringAction;
2use super::issue_type::IssueType;
3use super::severity::Severity;
4
5#[derive(Debug, Clone)]
7pub struct CouplingIssue {
8 pub issue_type: IssueType,
10 pub severity: Severity,
12 pub source: String,
14 pub target: String,
16 pub description: String,
18 pub refactoring: RefactoringAction,
20 pub balance_score: f64,
22}
23
24impl CouplingIssue {
25 pub fn stable_key(&self) -> IssueKey {
31 IssueKey {
32 issue_type: self.issue_type,
33 source: match self.issue_type {
34 IssueType::HighAfferentCoupling if is_count_target(&self.source, "dependents") => {
35 "<dependent-count>".to_string()
36 }
37 _ => self.source.clone(),
38 },
39 target: match self.issue_type {
40 IssueType::HighEfferentCoupling
41 if is_count_target(&self.target, "dependencies") =>
42 {
43 "<dependency-count>".to_string()
44 }
45 _ => self.target.clone(),
46 },
47 }
48 }
49
50 pub fn meets(&self, floor: Severity) -> bool {
52 self.severity >= floor
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
58pub struct IssueKey {
59 pub issue_type: IssueType,
61 pub source: String,
63 pub target: String,
65}
66
67impl From<&CouplingIssue> for IssueKey {
68 fn from(issue: &CouplingIssue) -> Self {
69 issue.stable_key()
70 }
71}
72
73fn is_count_target(value: &str, unit: &str) -> bool {
74 let Some((count, suffix)) = value.split_once(' ') else {
75 return false;
76 };
77 count.chars().all(|c| c.is_ascii_digit()) && suffix == unit
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn is_count_target_requires_digits_and_exact_unit() {
86 assert!(is_count_target("3 dependencies", "dependencies"));
87 assert!(is_count_target("12 dependents", "dependents"));
88 assert!(!is_count_target("3 widgets", "dependencies"));
90 assert!(!is_count_target("many dependencies", "dependencies"));
92 assert!(!is_count_target("dependencies", "dependencies"));
94 }
95
96 #[test]
97 fn meets_compares_severity_floor() {
98 let issue = CouplingIssue {
99 issue_type: IssueType::GodModule,
100 severity: Severity::Medium,
101 source: String::new(),
102 target: String::new(),
103 description: String::new(),
104 refactoring: RefactoringAction::General {
105 action: String::new(),
106 },
107 balance_score: 0.5,
108 };
109 assert!(issue.meets(Severity::Low));
110 assert!(issue.meets(Severity::Medium));
111 assert!(!issue.meets(Severity::High));
112 }
113}