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::analyses::EnrichedGraph;
49 use crate::config::Config;
50 use crate::graph::Graph;
51 use crate::graph::test_helpers::{make_edge, make_node};
52 use crate::rules::RuleContext;
53
54 fn make_enriched(graph: Graph) -> EnrichedGraph {
55 crate::analyses::enrich_graph(graph, std::path::Path::new("."), &Config::defaults(), None)
56 }
57
58 #[test]
59 fn no_fragility_in_cycle() {
60 let mut graph = Graph::new();
61 graph.add_node(make_node("a.md"));
62 graph.add_node(make_node("b.md"));
63 graph.add_node(make_node("c.md"));
64 graph.add_edge(make_edge("a.md", "b.md"));
65 graph.add_edge(make_edge("b.md", "c.md"));
66 graph.add_edge(make_edge("c.md", "a.md"));
67
68 let enriched = make_enriched(graph);
69 let ctx = RuleContext {
70 graph: &enriched,
71 options: None,
72 };
73 let diagnostics = FragilityRule.evaluate(&ctx);
74 assert!(diagnostics.is_empty());
75 }
76
77 #[test]
78 fn detects_cut_vertex_and_bridge() {
79 let mut graph = Graph::new();
80 graph.add_node(make_node("a.md"));
81 graph.add_node(make_node("b.md"));
82 graph.add_node(make_node("c.md"));
83 graph.add_edge(make_edge("a.md", "b.md"));
84 graph.add_edge(make_edge("b.md", "c.md"));
85
86 let enriched = make_enriched(graph);
87 let ctx = RuleContext {
88 graph: &enriched,
89 options: None,
90 };
91 let diagnostics = FragilityRule.evaluate(&ctx);
92 let cut_vertices: Vec<_> = diagnostics
93 .iter()
94 .filter(|d| d.message == "cut vertex")
95 .collect();
96 let bridges: Vec<_> = diagnostics
97 .iter()
98 .filter(|d| d.message == "bridge edge")
99 .collect();
100 assert_eq!(cut_vertices.len(), 1);
101 assert_eq!(bridges.len(), 2);
102 }
103}