Skip to main content

drft/rules/
unresolved_edge.rs

1use crate::diagnostic::Diagnostic;
2use crate::rules::{Rule, RuleContext};
3
4pub struct UnresolvedEdgeRule;
5
6impl Rule for UnresolvedEdgeRule {
7    fn name(&self) -> &str {
8        "unresolved-edge"
9    }
10
11    fn evaluate(&self, ctx: &RuleContext) -> Vec<Diagnostic> {
12        let graph = &ctx.graph.graph;
13
14        graph
15            .edges
16            .iter()
17            .filter_map(|edge| {
18                let target_node = graph.nodes.get(&edge.target);
19                if target_node.is_none_or(|n| !n.included || n.node_type.is_some()) {
20                    return None;
21                }
22
23                Some(Diagnostic {
24                    rule: "unresolved-edge".into(),
25                    message: "file not found".into(),
26                    source: Some(edge.source.clone()),
27                    target: Some(edge.target.clone()),
28                    fix: Some(format!(
29                        "{} does not exist \u{2014} either create it or update the link in {}",
30                        edge.target, edge.source
31                    )),
32                    ..Default::default()
33                })
34            })
35            .collect()
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::graph::test_helpers::{make_edge, make_enriched, make_node};
43    use crate::graph::{Edge, Graph, Node, NodeType};
44    use crate::rules::RuleContext;
45    use std::collections::HashMap;
46
47    #[test]
48    fn detects_unresolved_edge() {
49        let mut graph = Graph::new();
50        graph.add_node(make_node("index.md"));
51        graph.add_node(Node {
52            path: "gone.md".into(),
53            node_type: None,
54            included: true,
55            hash: None,
56            metadata: HashMap::new(),
57        });
58        graph.add_edge(Edge {
59            source: "index.md".into(),
60            target: "gone.md".into(),
61            link: None,
62            parser: "markdown".into(),
63        });
64
65        let enriched = make_enriched(graph);
66        let ctx = RuleContext {
67            graph: &enriched,
68            options: None,
69        };
70        let diagnostics = UnresolvedEdgeRule.evaluate(&ctx);
71        assert_eq!(diagnostics.len(), 1);
72        assert_eq!(diagnostics[0].rule, "unresolved-edge");
73        assert_eq!(diagnostics[0].source.as_deref(), Some("index.md"));
74        assert_eq!(diagnostics[0].target.as_deref(), Some("gone.md"));
75    }
76
77    #[test]
78    fn no_diagnostic_for_found_target() {
79        let mut graph = Graph::new();
80        graph.add_node(make_node("index.md"));
81        graph.add_node(make_node("setup.md"));
82        graph.add_edge(make_edge("index.md", "setup.md"));
83
84        let enriched = make_enriched(graph);
85        let ctx = RuleContext {
86            graph: &enriched,
87            options: None,
88        };
89        let diagnostics = UnresolvedEdgeRule.evaluate(&ctx);
90        assert!(diagnostics.is_empty());
91    }
92
93    #[test]
94    fn no_diagnostic_for_external_targets() {
95        let mut graph = Graph::new();
96        graph.add_node(make_node("index.md"));
97        graph.add_node(Node {
98            path: "assets/logo.png".into(),
99            node_type: Some(NodeType::File),
100            included: false,
101            hash: None,
102            metadata: HashMap::new(),
103        });
104        graph.add_node(Node {
105            path: "https://example.com".into(),
106            node_type: Some(NodeType::Uri),
107            included: false,
108            hash: None,
109            metadata: HashMap::new(),
110        });
111        graph.add_edge(Edge {
112            source: "index.md".into(),
113            target: "assets/logo.png".into(),
114            link: None,
115            parser: "markdown".into(),
116        });
117        graph.add_edge(Edge {
118            source: "index.md".into(),
119            target: "https://example.com".into(),
120            link: None,
121            parser: "markdown".into(),
122        });
123
124        let enriched = make_enriched(graph);
125        let ctx = RuleContext {
126            graph: &enriched,
127            options: None,
128        };
129        let diagnostics = UnresolvedEdgeRule.evaluate(&ctx);
130        assert!(diagnostics.is_empty());
131    }
132}