fraiseql_core/federation/
dependency_graph.rs1use std::collections::{HashMap, HashSet, VecDeque};
7
8use crate::federation::types::{FederationMetadata, FieldPathSelection};
9
10#[derive(Debug, Clone)]
12struct DependencyNode {
13 id: String,
15 requires: Vec<FieldPathSelection>,
17}
18
19#[derive(Debug, Clone)]
21struct DependencyEdge {
22 from: String,
24 to: String,
26}
27
28pub struct DependencyGraph {
33 nodes: HashMap<String, DependencyNode>,
35 edges: Vec<DependencyEdge>,
37}
38
39impl DependencyGraph {
40 pub fn build(metadata: &FederationMetadata) -> Result<Self, String> {
49 let mut nodes: HashMap<String, DependencyNode> = HashMap::new();
50 let mut edges = Vec::new();
51
52 for federated_type in &metadata.types {
54 for (field_name, directives) in &federated_type.field_directives {
55 if !directives.requires.is_empty() {
57 let node_id = format!("{}.{}", federated_type.name, field_name);
58
59 nodes.insert(
60 node_id.clone(),
61 DependencyNode {
62 id: node_id,
63 requires: directives.requires.clone(),
64 },
65 );
66 }
67 }
68 }
69
70 for node in nodes.values() {
73 for required in &node.requires {
74 let target_id = format!("{}.{}", required.typename, required.path.join("."));
76
77 edges.push(DependencyEdge {
78 from: node.id.clone(),
79 to: target_id,
80 });
81 }
82 }
83
84 Ok(DependencyGraph { nodes, edges })
85 }
86
87 pub fn node_count(&self) -> usize {
89 self.nodes.len()
90 }
91
92 pub fn has_node(&self, node_id: &str) -> bool {
94 self.nodes.contains_key(node_id)
95 }
96
97 fn edges_from(&self, node_id: &str) -> Vec<&DependencyEdge> {
99 self.edges.iter().filter(|e| e.from == node_id).collect()
100 }
101
102 pub fn detect_cycles(&self) -> Vec<Vec<String>> {
107 let mut visited = HashSet::new();
108 let mut rec_stack = HashSet::new();
109 let mut cycles = Vec::new();
110
111 for node_id in self.nodes.keys() {
112 if !visited.contains(node_id) {
113 self.dfs_cycle_detection(
114 node_id,
115 &mut visited,
116 &mut rec_stack,
117 &mut cycles,
118 &mut vec![],
119 );
120 }
121 }
122
123 cycles
124 }
125
126 fn dfs_cycle_detection(
128 &self,
129 node_id: &str,
130 visited: &mut HashSet<String>,
131 rec_stack: &mut HashSet<String>,
132 cycles: &mut Vec<Vec<String>>,
133 path: &mut Vec<String>,
134 ) {
135 visited.insert(node_id.to_string());
136 rec_stack.insert(node_id.to_string());
137 path.push(node_id.to_string());
138
139 for edge in self.edges_from(node_id) {
141 let next = &edge.to;
142
143 if !visited.contains(next) {
144 self.dfs_cycle_detection(next, visited, rec_stack, cycles, path);
145 } else if rec_stack.contains(next) {
146 if let Some(cycle_start) = path.iter().position(|x| x == next) {
148 let cycle: Vec<String> = path[cycle_start..].to_vec();
149 cycles.push(cycle);
150 }
151 }
152 }
153
154 rec_stack.remove(node_id);
155 path.pop();
156 }
157
158 pub fn topological_sort(&self) -> Result<Vec<String>, String> {
164 let cycles = self.detect_cycles();
166 if !cycles.is_empty() {
167 return Err(format!("Circular dependencies detected: {:?}", cycles));
168 }
169
170 let mut in_degree: HashMap<String, usize> = HashMap::new();
172
173 for node_id in self.nodes.keys() {
175 in_degree.insert(node_id.clone(), 0);
176 }
177
178 for edge in &self.edges {
180 if self.nodes.contains_key(&edge.to) {
181 *in_degree.get_mut(&edge.to).unwrap() += 1;
182 }
183 }
184
185 let mut queue: VecDeque<String> = in_degree
187 .iter()
188 .filter(|(_, degree)| **degree == 0)
189 .map(|(id, _)| id.clone())
190 .collect();
191
192 let mut result = Vec::new();
193
194 while let Some(node_id) = queue.pop_front() {
196 result.push(node_id.clone());
197
198 for edge in self.edges_from(&node_id) {
200 if let Some(target_degree) = in_degree.get_mut(&edge.to) {
202 *target_degree -= 1;
203
204 if *target_degree == 0 {
206 queue.push_back(edge.to.clone());
207 }
208 }
209 }
210 }
211
212 Ok(result)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_dependency_node_creation() {
222 let node = DependencyNode {
223 id: "User.orders".to_string(),
224 requires: vec![FieldPathSelection {
225 path: vec!["email".to_string()],
226 typename: "User".to_string(),
227 }],
228 };
229
230 assert_eq!(node.id, "User.orders");
231 assert_eq!(node.requires.len(), 1);
232 }
233
234 #[test]
235 fn test_empty_graph() {
236 let metadata = FederationMetadata {
237 enabled: true,
238 version: "v2".to_string(),
239 types: vec![],
240 };
241
242 let graph = DependencyGraph::build(&metadata).unwrap();
243 assert_eq!(graph.node_count(), 0);
244 assert!(graph.detect_cycles().is_empty());
245 }
246}