elif_core/container/
resolver.rs

1use crate::container::descriptor::{ServiceDescriptor, ServiceId};
2use crate::errors::CoreError;
3use std::collections::{HashMap, HashSet, VecDeque};
4
5/// Dependency resolution path for error reporting
6#[derive(Debug, Clone)]
7pub struct ResolutionPath {
8    pub services: Vec<ServiceId>,
9}
10
11impl Default for ResolutionPath {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl ResolutionPath {
18    /// Create a new resolution path
19    pub fn new() -> Self {
20        Self {
21            services: Vec::new(),
22        }
23    }
24
25    /// Add a service to the resolution path
26    pub fn push(&mut self, service_id: ServiceId) {
27        self.services.push(service_id);
28    }
29
30    /// Remove the last service from the resolution path
31    pub fn pop(&mut self) -> Option<ServiceId> {
32        self.services.pop()
33    }
34
35    /// Check if the path contains a service (for cycle detection)
36    pub fn contains(&self, service_id: &ServiceId) -> bool {
37        self.services.contains(service_id)
38    }
39
40    /// Get the path as a string for error messages
41    pub fn path_string(&self) -> String {
42        self.services
43            .iter()
44            .map(|id| {
45                format!(
46                    "{}({})",
47                    id.type_name(),
48                    id.name.as_deref().unwrap_or("default")
49                )
50            })
51            .collect::<Vec<_>>()
52            .join(" -> ")
53    }
54}
55
56/// Dependency graph node
57#[derive(Debug)]
58pub struct DependencyNode {
59    pub service_id: ServiceId,
60    pub dependencies: Vec<ServiceId>,
61    pub dependents: Vec<ServiceId>,
62}
63
64/// Dependency graph for analyzing service relationships
65#[derive(Debug)]
66pub struct DependencyGraph {
67    nodes: HashMap<ServiceId, DependencyNode>,
68}
69
70impl DependencyGraph {
71    /// Create a new dependency graph
72    pub fn new() -> Self {
73        Self {
74            nodes: HashMap::new(),
75        }
76    }
77
78    /// Build dependency graph from service descriptors
79    pub fn build_from_descriptors(descriptors: &[ServiceDescriptor]) -> Self {
80        let mut graph = Self::new();
81
82        // First pass: create all nodes
83        for descriptor in descriptors {
84            graph.add_service(&descriptor.service_id, &descriptor.dependencies);
85        }
86
87        // Second pass: build reverse dependencies
88        graph.build_reverse_dependencies();
89
90        graph
91    }
92
93    /// Add a service to the graph
94    pub fn add_service(&mut self, service_id: &ServiceId, dependencies: &[ServiceId]) {
95        let node = DependencyNode {
96            service_id: service_id.clone(),
97            dependencies: dependencies.to_vec(),
98            dependents: Vec::new(),
99        };
100        self.nodes.insert(service_id.clone(), node);
101    }
102
103    /// Build reverse dependency relationships
104    fn build_reverse_dependencies(&mut self) {
105        let dependencies: Vec<(ServiceId, Vec<ServiceId>)> = self
106            .nodes
107            .iter()
108            .map(|(id, node)| (id.clone(), node.dependencies.clone()))
109            .collect();
110
111        for (service_id, deps) in dependencies {
112            for dep_id in deps {
113                if let Some(dep_node) = self.nodes.get_mut(&dep_id) {
114                    dep_node.dependents.push(service_id.clone());
115                }
116            }
117        }
118    }
119
120    /// Detect circular dependencies
121    pub fn detect_cycles(&self) -> Result<(), CoreError> {
122        let mut visited = HashSet::new();
123        let mut in_progress = HashSet::new();
124
125        for service_id in self.nodes.keys() {
126            if !visited.contains(service_id) {
127                let mut path = ResolutionPath::new();
128                self.detect_cycle_dfs(service_id, &mut visited, &mut in_progress, &mut path)?;
129            }
130        }
131
132        Ok(())
133    }
134
135    /// DFS-based cycle detection
136    fn detect_cycle_dfs(
137        &self,
138        service_id: &ServiceId,
139        visited: &mut HashSet<ServiceId>,
140        in_progress: &mut HashSet<ServiceId>,
141        path: &mut ResolutionPath,
142    ) -> Result<(), CoreError> {
143        if in_progress.contains(service_id) {
144            path.push(service_id.clone());
145            return Err(CoreError::CircularDependency {
146                path: path.path_string(),
147                cycle_service: format!(
148                    "{}({})",
149                    service_id.type_name(),
150                    service_id.name.as_deref().unwrap_or("default")
151                ),
152            });
153        }
154
155        if visited.contains(service_id) {
156            return Ok(());
157        }
158
159        in_progress.insert(service_id.clone());
160        path.push(service_id.clone());
161
162        if let Some(node) = self.nodes.get(service_id) {
163            for dep_id in &node.dependencies {
164                self.detect_cycle_dfs(dep_id, visited, in_progress, path)?;
165            }
166        }
167
168        path.pop();
169        in_progress.remove(service_id);
170        visited.insert(service_id.clone());
171
172        Ok(())
173    }
174
175    /// Get topological order for dependency resolution
176    pub fn topological_sort(&self) -> Result<Vec<ServiceId>, CoreError> {
177        self.detect_cycles()?;
178
179        let mut in_degree: HashMap<ServiceId, usize> = HashMap::new();
180        let mut queue = VecDeque::new();
181        let mut result = Vec::new();
182
183        // Calculate in-degrees
184        for (service_id, node) in &self.nodes {
185            in_degree.insert(service_id.clone(), node.dependencies.len());
186            if node.dependencies.is_empty() {
187                queue.push_back(service_id.clone());
188            }
189        }
190
191        // Process queue
192        while let Some(service_id) = queue.pop_front() {
193            result.push(service_id.clone());
194
195            if let Some(node) = self.nodes.get(&service_id) {
196                for dependent_id in &node.dependents {
197                    if let Some(degree) = in_degree.get_mut(dependent_id) {
198                        *degree -= 1;
199                        if *degree == 0 {
200                            queue.push_back(dependent_id.clone());
201                        }
202                    }
203                }
204            }
205        }
206
207        if result.len() != self.nodes.len() {
208            return Err(CoreError::CircularDependency {
209                path: "Complex circular dependency detected".to_string(),
210                cycle_service: "Multiple services".to_string(),
211            });
212        }
213
214        Ok(result)
215    }
216
217    /// Get dependencies for a service
218    pub fn get_dependencies(&self, service_id: &ServiceId) -> Option<&[ServiceId]> {
219        self.nodes
220            .get(service_id)
221            .map(|node| node.dependencies.as_slice())
222    }
223
224    /// Get dependents for a service
225    pub fn get_dependents(&self, service_id: &ServiceId) -> Option<&[ServiceId]> {
226        self.nodes
227            .get(service_id)
228            .map(|node| node.dependents.as_slice())
229    }
230}
231
232impl Default for DependencyGraph {
233    fn default() -> Self {
234        Self::new()
235    }
236}
237
238/// Dependency resolver for managing service resolution
239#[derive(Debug)]
240pub struct DependencyResolver {
241    graph: DependencyGraph,
242    resolution_order: Vec<ServiceId>,
243}
244
245impl DependencyResolver {
246    /// Create a new dependency resolver
247    pub fn new(descriptors: &[ServiceDescriptor]) -> Result<Self, CoreError> {
248        let graph = DependencyGraph::build_from_descriptors(descriptors);
249        let resolution_order = graph.topological_sort()?;
250
251        Ok(Self {
252            graph,
253            resolution_order,
254        })
255    }
256
257    /// Get the resolution order for services
258    pub fn resolution_order(&self) -> &[ServiceId] {
259        &self.resolution_order
260    }
261
262    /// Get dependencies for a service
263    pub fn get_dependencies(&self, service_id: &ServiceId) -> Option<&[ServiceId]> {
264        self.graph.get_dependencies(service_id)
265    }
266
267    /// Validate that all dependencies can be satisfied
268    pub fn validate_dependencies(
269        &self,
270        available_services: &HashSet<ServiceId>,
271    ) -> Result<(), CoreError> {
272        for service_id in &self.resolution_order {
273            if let Some(dependencies) = self.get_dependencies(service_id) {
274                for dep_id in dependencies {
275                    if !available_services.contains(dep_id) {
276                        return Err(CoreError::ServiceNotFound {
277                            service_type: format!(
278                                "{}({})",
279                                dep_id.type_name(),
280                                dep_id.name.as_deref().unwrap_or("default")
281                            ),
282                        });
283                    }
284                }
285            }
286        }
287        Ok(())
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294    use std::any::TypeId;
295
296    #[test]
297    fn test_service_id_creation() {
298        let id1 = ServiceId::of::<String>();
299        let id2 = ServiceId::named::<String>("cache");
300
301        assert_eq!(id1.type_id, TypeId::of::<String>());
302        assert_eq!(id1.name, None);
303
304        assert_eq!(id2.type_id, TypeId::of::<String>());
305        assert_eq!(id2.name, Some("cache".to_string()));
306
307        assert_ne!(id1, id2);
308    }
309
310    #[test]
311    fn test_dependency_graph_cycle_detection() {
312        let mut graph = DependencyGraph::new();
313
314        let service_a = ServiceId::named::<String>("A");
315        let service_b = ServiceId::named::<String>("B");
316        let service_c = ServiceId::named::<String>("C");
317
318        // Create cycle: A -> B -> C -> A
319        graph.add_service(&service_a, &[service_b.clone()]);
320        graph.add_service(&service_b, &[service_c.clone()]);
321        graph.add_service(&service_c, &[service_a.clone()]);
322
323        graph.build_reverse_dependencies();
324
325        let result = graph.detect_cycles();
326        assert!(result.is_err());
327
328        if let Err(CoreError::CircularDependency { path, .. }) = result {
329            assert!(path.contains("A") && path.contains("B") && path.contains("C"));
330        }
331    }
332
333    #[test]
334    fn test_dependency_graph_topological_sort() {
335        let mut graph = DependencyGraph::new();
336
337        let service_a = ServiceId::named::<String>("A");
338        let service_b = ServiceId::named::<String>("B");
339        let service_c = ServiceId::named::<String>("C");
340
341        // Create valid dependency chain: C -> B -> A (A depends on B, B depends on C)
342        graph.add_service(&service_c, &[]);
343        graph.add_service(&service_b, &[service_c.clone()]);
344        graph.add_service(&service_a, &[service_b.clone()]);
345
346        graph.build_reverse_dependencies();
347
348        let sorted = graph.topological_sort().unwrap();
349
350        // C should come before B, B should come before A
351        let pos_c = sorted.iter().position(|id| id == &service_c).unwrap();
352        let pos_b = sorted.iter().position(|id| id == &service_b).unwrap();
353        let pos_a = sorted.iter().position(|id| id == &service_a).unwrap();
354
355        assert!(pos_c < pos_b);
356        assert!(pos_b < pos_a);
357    }
358
359    #[test]
360    fn test_resolution_path() {
361        let mut path = ResolutionPath::new();
362        let service_a = ServiceId::named::<String>("A");
363        let service_b = ServiceId::named::<String>("B");
364
365        path.push(service_a.clone());
366        path.push(service_b.clone());
367
368        assert!(path.contains(&service_a));
369        assert!(path.contains(&service_b));
370
371        let popped = path.pop();
372        assert_eq!(popped, Some(service_b.clone()));
373        assert!(!path.contains(&service_b));
374        assert!(path.contains(&service_a));
375    }
376}