drft/rules/
unresolved_edge.rs1use 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}