1use crate::analyses::Analysis;
2use crate::analyses::AnalysisContext;
3use crate::analyses::bridges::Bridges;
4use crate::diagnostic::Diagnostic;
5use crate::rules::{Rule, RuleContext};
6
7pub struct FragilityRule;
8
9impl Rule for FragilityRule {
10 fn name(&self) -> &str {
11 "fragility"
12 }
13
14 fn evaluate(&self, ctx: &RuleContext) -> Vec<Diagnostic> {
15 let analysis_ctx = AnalysisContext {
16 graph: ctx.graph,
17 root: ctx.root,
18 config: ctx.config,
19 lockfile: ctx.lockfile,
20 };
21 let result = Bridges.run(&analysis_ctx);
22 let mut diagnostics = Vec::new();
23
24 for vertex in &result.cut_vertices {
25 diagnostics.push(Diagnostic {
26 rule: "fragility".into(),
27 message: "cut vertex".into(),
28 node: Some(vertex.clone()),
29 fix: Some(format!(
30 "{vertex} is a single point of failure \u{2014} removing it disconnects the graph. Consider adding alternative paths."
31 )),
32 ..Default::default()
33 });
34 }
35
36 for bridge in &result.bridges {
37 diagnostics.push(Diagnostic {
38 rule: "fragility".into(),
39 message: "bridge edge".into(),
40 source: Some(bridge.source.clone()),
41 target: Some(bridge.target.clone()),
42 fix: Some(format!(
43 "{} \u{2194} {} is the only connection between two parts of the graph \u{2014} consider adding alternative paths",
44 bridge.source, bridge.target
45 )),
46 ..Default::default()
47 });
48 }
49
50 diagnostics
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::config::Config;
58 use crate::graph::Graph;
59 use crate::graph::test_helpers::{make_edge, make_node};
60 use crate::rules::RuleContext;
61 use std::path::Path;
62
63 fn make_ctx<'a>(graph: &'a Graph, config: &'a Config) -> RuleContext<'a> {
64 RuleContext {
65 graph,
66 root: Path::new("."),
67 config,
68 lockfile: None,
69 }
70 }
71
72 #[test]
73 fn no_fragility_in_cycle() {
74 let mut graph = Graph::new();
75 graph.add_node(make_node("a.md"));
76 graph.add_node(make_node("b.md"));
77 graph.add_node(make_node("c.md"));
78 graph.add_edge(make_edge("a.md", "b.md"));
79 graph.add_edge(make_edge("b.md", "c.md"));
80 graph.add_edge(make_edge("c.md", "a.md"));
81
82 let config = Config::defaults();
83 let diagnostics = FragilityRule.evaluate(&make_ctx(&graph, &config));
84 assert!(diagnostics.is_empty());
85 }
86
87 #[test]
88 fn detects_cut_vertex_and_bridge() {
89 let mut graph = Graph::new();
90 graph.add_node(make_node("a.md"));
91 graph.add_node(make_node("b.md"));
92 graph.add_node(make_node("c.md"));
93 graph.add_edge(make_edge("a.md", "b.md"));
94 graph.add_edge(make_edge("b.md", "c.md"));
95
96 let config = Config::defaults();
97 let diagnostics = FragilityRule.evaluate(&make_ctx(&graph, &config));
98 let cut_vertices: Vec<_> = diagnostics
99 .iter()
100 .filter(|d| d.message == "cut vertex")
101 .collect();
102 let bridges: Vec<_> = diagnostics
103 .iter()
104 .filter(|d| d.message == "bridge edge")
105 .collect();
106 assert_eq!(cut_vertices.len(), 1);
107 assert_eq!(bridges.len(), 2);
108 }
109}