agentic_codebase/graph/
traversal.rs1use std::collections::{HashSet, VecDeque};
7
8use crate::types::EdgeType;
9
10use super::code_graph::CodeGraph;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Direction {
15 Forward,
17 Backward,
19}
20
21#[derive(Debug, Clone)]
23pub struct TraversalOptions {
24 pub max_depth: i32,
26 pub edge_types: Vec<EdgeType>,
28 pub direction: Direction,
30}
31
32impl Default for TraversalOptions {
33 fn default() -> Self {
34 Self {
35 max_depth: -1,
36 edge_types: Vec::new(),
37 direction: Direction::Forward,
38 }
39 }
40}
41
42pub type TraversalResult = Vec<(u64, u32)>;
44
45pub fn bfs(graph: &CodeGraph, start_id: u64, options: &TraversalOptions) -> TraversalResult {
49 let mut visited = HashSet::new();
50 let mut queue = VecDeque::new();
51 let mut result = Vec::new();
52
53 visited.insert(start_id);
54 queue.push_back((start_id, 0u32));
55
56 while let Some((current_id, depth)) = queue.pop_front() {
57 result.push((current_id, depth));
58
59 if options.max_depth >= 0 && depth >= options.max_depth as u32 {
61 continue;
62 }
63
64 let neighbors = match options.direction {
65 Direction::Forward => graph.edges_from(current_id),
66 Direction::Backward => graph.edges_to(current_id),
67 };
68
69 for edge in neighbors {
70 if !options.edge_types.is_empty() && !options.edge_types.contains(&edge.edge_type) {
72 continue;
73 }
74
75 let next_id = match options.direction {
76 Direction::Forward => edge.target_id,
77 Direction::Backward => edge.source_id,
78 };
79
80 if visited.insert(next_id) {
81 queue.push_back((next_id, depth + 1));
82 }
83 }
84 }
85
86 result
87}
88
89pub fn dfs(graph: &CodeGraph, start_id: u64, options: &TraversalOptions) -> TraversalResult {
93 let mut visited = HashSet::new();
94 let mut result = Vec::new();
95 dfs_inner(graph, start_id, 0, options, &mut visited, &mut result);
96 result
97}
98
99fn dfs_inner(
100 graph: &CodeGraph,
101 current_id: u64,
102 depth: u32,
103 options: &TraversalOptions,
104 visited: &mut HashSet<u64>,
105 result: &mut TraversalResult,
106) {
107 if !visited.insert(current_id) {
108 return;
109 }
110
111 result.push((current_id, depth));
112
113 if options.max_depth >= 0 && depth >= options.max_depth as u32 {
114 return;
115 }
116
117 let neighbors = match options.direction {
118 Direction::Forward => graph.edges_from(current_id),
119 Direction::Backward => graph.edges_to(current_id),
120 };
121
122 for edge in neighbors {
123 if !options.edge_types.is_empty() && !options.edge_types.contains(&edge.edge_type) {
124 continue;
125 }
126
127 let next_id = match options.direction {
128 Direction::Forward => edge.target_id,
129 Direction::Backward => edge.source_id,
130 };
131
132 dfs_inner(graph, next_id, depth + 1, options, visited, result);
133 }
134}
135
136pub fn find_paths(
138 graph: &CodeGraph,
139 from: u64,
140 to: u64,
141 max_depth: u32,
142 edge_types: &[EdgeType],
143) -> Vec<Vec<u64>> {
144 let mut paths = Vec::new();
145 let mut current_path = vec![from];
146 let mut visited = HashSet::new();
147 visited.insert(from);
148 find_paths_inner(
149 graph,
150 to,
151 max_depth,
152 edge_types,
153 &mut current_path,
154 &mut visited,
155 &mut paths,
156 );
157 paths
158}
159
160fn find_paths_inner(
161 graph: &CodeGraph,
162 target: u64,
163 max_depth: u32,
164 edge_types: &[EdgeType],
165 current_path: &mut Vec<u64>,
166 visited: &mut HashSet<u64>,
167 results: &mut Vec<Vec<u64>>,
168) {
169 let current = *current_path.last().unwrap();
170
171 if current == target && current_path.len() > 1 {
172 results.push(current_path.clone());
173 return;
174 }
175
176 if current_path.len() > max_depth as usize {
177 return;
178 }
179
180 for edge in graph.edges_from(current) {
181 if !edge_types.is_empty() && !edge_types.contains(&edge.edge_type) {
182 continue;
183 }
184
185 if visited.insert(edge.target_id) {
186 current_path.push(edge.target_id);
187 find_paths_inner(
188 graph,
189 target,
190 max_depth,
191 edge_types,
192 current_path,
193 visited,
194 results,
195 );
196 current_path.pop();
197 visited.remove(&edge.target_id);
198 }
199 }
200}
201
202pub fn shortest_path(
206 graph: &CodeGraph,
207 from: u64,
208 to: u64,
209 edge_types: &[EdgeType],
210) -> Option<Vec<u64>> {
211 if from == to {
212 return Some(vec![from]);
213 }
214
215 let mut visited = HashSet::new();
216 let mut queue = VecDeque::new();
217 let mut parent: std::collections::HashMap<u64, u64> = std::collections::HashMap::new();
218
219 visited.insert(from);
220 queue.push_back(from);
221
222 while let Some(current) = queue.pop_front() {
223 for edge in graph.edges_from(current) {
224 if !edge_types.is_empty() && !edge_types.contains(&edge.edge_type) {
225 continue;
226 }
227
228 if visited.insert(edge.target_id) {
229 parent.insert(edge.target_id, current);
230
231 if edge.target_id == to {
232 let mut path = vec![to];
234 let mut node = to;
235 while let Some(&p) = parent.get(&node) {
236 path.push(p);
237 node = p;
238 }
239 path.reverse();
240 return Some(path);
241 }
242
243 queue.push_back(edge.target_id);
244 }
245 }
246 }
247
248 None
249}