drft/rules/
boundary_violation.rs1use crate::diagnostic::Diagnostic;
2use crate::rules::{Rule, RuleContext};
3
4pub struct BoundaryViolationRule;
5
6impl Rule for BoundaryViolationRule {
7 fn name(&self) -> &str {
8 "boundary-violation"
9 }
10
11 fn evaluate(&self, ctx: &RuleContext) -> Vec<Diagnostic> {
12 let result = &ctx.graph.graph_boundaries;
13
14 if !result.sealed {
15 return vec![];
16 }
17
18 result
19 .escapes
20 .iter()
21 .map(|e| Diagnostic {
22 rule: "boundary-violation".into(),
23 message: "links outside graph boundary".into(),
24 source: Some(e.source.clone()),
25 target: Some(e.target.clone()),
26 fix: Some(format!(
27 "link reaches outside the graph \u{2014} move {} into the graph or remove the link from {}",
28 e.target, e.source
29 )),
30 ..Default::default()
31 })
32 .collect()
33 }
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39 use crate::config::Config;
40 use crate::graph::{Edge, Graph, Node, NodeType};
41 use crate::rules::RuleContext;
42 use std::collections::HashMap;
43 use std::fs;
44 use tempfile::TempDir;
45
46 fn make_enriched(graph: Graph, root: &std::path::Path) -> crate::analyses::EnrichedGraph {
47 crate::analyses::enrich_graph(graph, root, &Config::defaults(), None)
48 }
49
50 #[test]
51 fn detects_escape() {
52 let dir = TempDir::new().unwrap();
53 fs::write(dir.path().join("drft.lock"), "lockfile_version = 1\n").unwrap();
54
55 let mut graph = Graph::new();
56 graph.add_node(Node {
57 path: "index.md".into(),
58 node_type: NodeType::File,
59 hash: None,
60 graph: None,
61 metadata: HashMap::new(),
62 });
63 graph.add_edge(Edge {
64 source: "index.md".into(),
65 target: "../README.md".into(),
66 link: None,
67 parser: "markdown".into(),
68 });
69
70 let enriched = make_enriched(graph, dir.path());
71 let ctx = RuleContext {
72 graph: &enriched,
73 options: None,
74 };
75 let diagnostics = BoundaryViolationRule.evaluate(&ctx);
76 assert_eq!(diagnostics.len(), 1);
77 assert_eq!(diagnostics[0].rule, "boundary-violation");
78 assert_eq!(diagnostics[0].target.as_deref(), Some("../README.md"));
79 }
80
81 #[test]
82 fn detects_deep_escape() {
83 let dir = TempDir::new().unwrap();
84 fs::write(dir.path().join("drft.lock"), "lockfile_version = 1\n").unwrap();
85
86 let mut graph = Graph::new();
87 graph.add_node(Node {
88 path: "index.md".into(),
89 node_type: NodeType::File,
90 hash: None,
91 graph: None,
92 metadata: HashMap::new(),
93 });
94 graph.add_edge(Edge {
95 source: "index.md".into(),
96 target: "../../other.md".into(),
97 link: None,
98 parser: "markdown".into(),
99 });
100
101 let enriched = make_enriched(graph, dir.path());
102 let ctx = RuleContext {
103 graph: &enriched,
104 options: None,
105 };
106 let diagnostics = BoundaryViolationRule.evaluate(&ctx);
107 assert_eq!(diagnostics.len(), 1);
108 }
109
110 #[test]
111 fn no_violation_for_internal_link() {
112 let dir = TempDir::new().unwrap();
113 fs::write(dir.path().join("drft.lock"), "lockfile_version = 1\n").unwrap();
114
115 let mut graph = Graph::new();
116 graph.add_node(Node {
117 path: "index.md".into(),
118 node_type: NodeType::File,
119 hash: None,
120 graph: None,
121 metadata: HashMap::new(),
122 });
123 graph.add_edge(Edge {
124 source: "index.md".into(),
125 target: "setup.md".into(),
126 link: None,
127 parser: "markdown".into(),
128 });
129
130 let enriched = make_enriched(graph, dir.path());
131 let ctx = RuleContext {
132 graph: &enriched,
133 options: None,
134 };
135 let diagnostics = BoundaryViolationRule.evaluate(&ctx);
136 assert!(diagnostics.is_empty());
137 }
138
139 #[test]
140 fn vacuous_without_lockfile() {
141 let dir = TempDir::new().unwrap();
142
143 let mut graph = Graph::new();
144 graph.add_edge(Edge {
145 source: "index.md".into(),
146 target: "../escape.md".into(),
147 link: None,
148 parser: "markdown".into(),
149 });
150
151 let enriched = make_enriched(graph, dir.path());
152 let ctx = RuleContext {
153 graph: &enriched,
154 options: None,
155 };
156 let diagnostics = BoundaryViolationRule.evaluate(&ctx);
157 assert!(
158 diagnostics.is_empty(),
159 "no lockfile means no boundary to enforce"
160 );
161 }
162}