Skip to main content

drft/rules/
fragility.rs

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}