ralph/queue/graph/
traversal.rs1use super::types::{BoundedChainResult, DependencyGraph, TaskNode};
17use std::collections::HashSet;
18
19fn collect_chain_unbounded(
20 graph: &DependencyGraph,
21 start: &str,
22 mut neighbors: impl FnMut(&TaskNode) -> Vec<String>,
23) -> Vec<String> {
24 let mut chain = Vec::new();
25 let mut visited = HashSet::new();
26 let mut stack = vec![start.to_string()];
27
28 while let Some(current_id) = stack.pop() {
29 if visited.contains(¤t_id) {
30 continue;
31 }
32 visited.insert(current_id.clone());
33
34 if let Some(node) = graph.get(¤t_id) {
35 for next_id in neighbors(node) {
36 if !visited.contains(&next_id) {
37 chain.push(next_id.clone());
38 stack.push(next_id);
39 }
40 }
41 }
42 }
43
44 chain
45}
46
47fn collect_chain_bounded(
48 graph: &DependencyGraph,
49 start: &str,
50 limit: usize,
51 mut neighbors: impl FnMut(&TaskNode) -> Vec<String>,
52) -> BoundedChainResult {
53 if limit == 0 {
54 let mut visited = HashSet::new();
55 let mut stack = vec![start.to_string()];
56
57 while let Some(current_id) = stack.pop() {
58 if visited.contains(¤t_id) {
59 continue;
60 }
61 visited.insert(current_id.clone());
62
63 if let Some(node) = graph.get(¤t_id) {
64 for next_id in neighbors(node) {
65 if !visited.contains(&next_id) {
66 return BoundedChainResult {
67 task_ids: Vec::new(),
68 truncated: true,
69 };
70 }
71 }
72 }
73 }
74
75 return BoundedChainResult {
76 task_ids: Vec::new(),
77 truncated: false,
78 };
79 }
80
81 let mut result = BoundedChainResult {
82 task_ids: Vec::new(),
83 truncated: false,
84 };
85
86 let mut visited = HashSet::new();
87 let mut stack = vec![start.to_string()];
88
89 while let Some(current_id) = stack.pop() {
90 if visited.contains(¤t_id) {
91 continue;
92 }
93 visited.insert(current_id.clone());
94
95 if let Some(node) = graph.get(¤t_id) {
96 for next_id in neighbors(node) {
97 if !visited.contains(&next_id) {
98 if result.task_ids.len() < limit {
99 result.task_ids.push(next_id.clone());
100 } else {
101 result.truncated = true;
102 }
103 stack.push(next_id);
104 }
105 }
106 }
107 }
108
109 result
110}
111
112impl DependencyGraph {
113 pub fn get_blocking_chain(&self, task_id: &str) -> Vec<String> {
114 collect_chain_unbounded(self, task_id, |n| n.dependencies.clone())
115 }
116
117 pub fn get_blocked_chain(&self, task_id: &str) -> Vec<String> {
118 collect_chain_unbounded(self, task_id, |n| n.dependents.clone())
119 }
120
121 pub fn get_blocking_chain_bounded(&self, task_id: &str, limit: usize) -> BoundedChainResult {
122 collect_chain_bounded(self, task_id, limit, |n| n.dependencies.clone())
123 }
124
125 pub fn get_blocked_chain_bounded(&self, task_id: &str, limit: usize) -> BoundedChainResult {
126 collect_chain_bounded(self, task_id, limit, |n| n.dependents.clone())
127 }
128
129 pub fn get_blocks_chain(&self, task_id: &str) -> Vec<String> {
130 collect_chain_unbounded(self, task_id, |n| n.blocks.clone())
131 }
132
133 pub fn get_blocked_by_chain(&self, task_id: &str) -> Vec<String> {
134 collect_chain_unbounded(self, task_id, |n| n.blocked_by.clone())
135 }
136
137 pub fn get_related_chain(&self, task_id: &str) -> Vec<String> {
138 collect_chain_unbounded(self, task_id, |n| {
139 let mut out = Vec::with_capacity(n.relates_to.len() + n.related_by.len());
140 out.extend(n.relates_to.iter().cloned());
141 out.extend(n.related_by.iter().cloned());
142 out
143 })
144 }
145
146 pub fn get_duplicate_chain(&self, task_id: &str) -> Vec<String> {
147 let mut chain = Vec::new();
148 let mut visited = HashSet::new();
149 let mut current = task_id.to_string();
150
151 loop {
152 if visited.contains(¤t) {
153 break;
154 }
155 visited.insert(current.clone());
156
157 if let Some(node) = self.get(¤t)
158 && let Some(duplicates) = &node.duplicates
159 && !visited.contains(duplicates)
160 {
161 chain.push(duplicates.clone());
162 current = duplicates.clone();
163 continue;
164 }
165 break;
166 }
167
168 if let Some(node) = self.get(task_id) {
169 for dupe_id in &node.duplicated_by {
170 if !visited.contains(dupe_id) {
171 chain.push(dupe_id.clone());
172 }
173 }
174 }
175
176 chain
177 }
178
179 pub fn get_immediate_dependencies(&self, task_id: &str) -> Vec<String> {
180 self.get(task_id)
181 .map(|n| n.dependencies.clone())
182 .unwrap_or_default()
183 }
184
185 pub fn get_immediate_dependents(&self, task_id: &str) -> Vec<String> {
186 self.get(task_id)
187 .map(|n| n.dependents.clone())
188 .unwrap_or_default()
189 }
190
191 pub fn get_immediate_blocks(&self, task_id: &str) -> Vec<String> {
192 self.get(task_id)
193 .map(|n| n.blocks.clone())
194 .unwrap_or_default()
195 }
196
197 pub fn get_immediate_blocked_by(&self, task_id: &str) -> Vec<String> {
198 self.get(task_id)
199 .map(|n| n.blocked_by.clone())
200 .unwrap_or_default()
201 }
202
203 pub fn get_immediate_relates_to(&self, task_id: &str) -> Vec<String> {
204 self.get(task_id)
205 .map(|n| n.relates_to.clone())
206 .unwrap_or_default()
207 }
208
209 pub fn get_immediate_duplicated_by(&self, task_id: &str) -> Vec<String> {
210 self.get(task_id)
211 .map(|n| n.duplicated_by.clone())
212 .unwrap_or_default()
213 }
214}