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