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::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}