Skip to main content

memlink_runtime/
deps.rs

1//! Module dependency tracking and composition.
2//!
3//! Manages dependencies between modules, load order, and nested calls.
4
5use std::collections::{HashMap, HashSet, VecDeque};
6
7use dashmap::DashMap;
8
9use crate::error::{Error, Result};
10
11/// Dependency information for a module.
12#[derive(Debug, Clone)]
13pub struct ModuleDependency {
14    /// Name of the required module.
15    pub name: String,
16    /// Minimum version required (optional).
17    pub min_version: Option<String>,
18    /// Whether this dependency is optional.
19    pub optional: bool,
20}
21
22/// Dependency graph node.
23#[derive(Debug, Clone)]
24pub struct DependencyNode {
25    /// Module name.
26    pub name: String,
27    /// Dependencies this module requires.
28    pub dependencies: Vec<ModuleDependency>,
29    /// Modules that depend on this one (reverse deps).
30    pub dependents: HashSet<String>,
31    /// Whether the module is loaded.
32    pub loaded: bool,
33}
34
35/// Module dependency graph.
36#[derive(Debug)]
37pub struct DependencyGraph {
38    /// All nodes in the graph.
39    nodes: DashMap<String, DependencyNode>,
40}
41
42impl DependencyGraph {
43    /// Creates a new empty dependency graph.
44    pub fn new() -> Self {
45        Self {
46            nodes: DashMap::new(),
47        }
48    }
49
50    /// Adds a module to the graph.
51    pub fn add_module(&self, name: &str, dependencies: Vec<ModuleDependency>) {
52        let node = DependencyNode {
53            name: name.to_string(),
54            dependencies,
55            dependents: HashSet::new(),
56            loaded: false,
57        };
58        self.nodes.insert(name.to_string(), node);
59    }
60
61    /// Marks a module as loaded.
62    pub fn mark_loaded(&self, name: &str) {
63        if let Some(mut node) = self.nodes.get_mut(name) {
64            node.loaded = true;
65        }
66    }
67
68    /// Marks a module as unloaded.
69    pub fn mark_unloaded(&self, name: &str) {
70        if let Some(mut node) = self.nodes.get_mut(name) {
71            node.loaded = false;
72        }
73    }
74
75    /// Checks if all dependencies of a module are satisfied.
76    pub fn dependencies_satisfied(&self, name: &str) -> bool {
77        let node = match self.nodes.get(name) {
78            Some(n) => n,
79            None => return false,
80        };
81
82        for dep in &node.dependencies {
83            if dep.optional {
84                continue;
85            }
86
87            let dep_node = match self.nodes.get(&dep.name) {
88                Some(n) => n,
89                None => return false, // Dependency not registered
90            };
91
92            if !dep_node.loaded {
93                return false; // Dependency not loaded
94            }
95        }
96
97        true
98    }
99
100    /// Returns the load order for all modules (topological sort).
101    ///
102    /// Returns modules in order they should be loaded (dependencies first).
103    pub fn load_order(&self) -> Result<Vec<String>> {
104        // Kahn's algorithm for topological sort
105        let mut in_degree: HashMap<String, usize> = HashMap::new();
106        let mut queue: VecDeque<String> = VecDeque::new();
107        let mut result: Vec<String> = Vec::new();
108
109        // Calculate in-degrees
110        for entry in self.nodes.iter() {
111            let name = entry.key().clone();
112            let node = entry.value();
113            let required_deps = node.dependencies.iter().filter(|d| !d.optional).count();
114            in_degree.insert(name.clone(), required_deps);
115
116            if required_deps == 0 {
117                queue.push_back(name.clone());
118            }
119        }
120
121        // Process queue
122        while let Some(name) = queue.pop_front() {
123            result.push(name.clone());
124
125            // Reduce in-degree for dependents
126            for entry in self.nodes.iter() {
127                let node = entry.value();
128                if node.dependencies.iter().any(|d| d.name == name && !d.optional) {
129                    let in_deg = in_degree.get_mut(entry.key()).unwrap();
130                    *in_deg -= 1;
131                    if *in_deg == 0 {
132                        queue.push_back(entry.key().clone());
133                    }
134                }
135            }
136        }
137
138        // Check for cycles
139        if result.len() != self.nodes.len() {
140            return Err(Error::ModuleCallFailed(-1)); // Would be CircularDependency error
141        }
142
143        Ok(result)
144    }
145
146    /// Returns the unload order (reverse of load order).
147    pub fn unload_order(&self) -> Result<Vec<String>> {
148        let mut order = self.load_order()?;
149        order.reverse();
150        Ok(order)
151    }
152
153    /// Detects circular dependencies.
154    pub fn has_cycle(&self) -> bool {
155        self.load_order().is_err()
156    }
157
158    /// Returns all dependencies (transitive) for a module.
159    pub fn all_dependencies(&self, name: &str) -> HashSet<String> {
160        let mut result = HashSet::new();
161        let mut queue: VecDeque<String> = VecDeque::new();
162
163        if let Some(node) = self.nodes.get(name) {
164            for dep in &node.dependencies {
165                queue.push_back(dep.name.clone());
166            }
167        }
168
169        while let Some(dep_name) = queue.pop_front() {
170            if result.contains(&dep_name) {
171                continue;
172            }
173            result.insert(dep_name.clone());
174
175            if let Some(dep_node) = self.nodes.get(&dep_name) {
176                for sub_dep in &dep_node.dependencies {
177                    queue.push_back(sub_dep.name.clone());
178                }
179            }
180        }
181
182        result
183    }
184
185    /// Returns all modules that depend on the given module (transitive).
186    pub fn all_dependents(&self, name: &str) -> HashSet<String> {
187        let mut result = HashSet::new();
188        let mut queue: VecDeque<String> = VecDeque::new();
189
190        // Find direct dependents
191        for entry in self.nodes.iter() {
192            let node = entry.value();
193            if node.dependencies.iter().any(|d| d.name == name) {
194                queue.push_back(entry.key().clone());
195            }
196        }
197
198        while let Some(dep_name) = queue.pop_front() {
199            if result.contains(&dep_name) {
200                continue;
201            }
202            result.insert(dep_name.clone());
203
204            // Find dependents of this dependent
205            for entry in self.nodes.iter() {
206                let node = entry.value();
207                if node.dependencies.iter().any(|d| d.name == dep_name) {
208                    queue.push_back(entry.key().clone());
209                }
210            }
211        }
212
213        result
214    }
215
216    /// Checks if a module can be safely unloaded (no loaded dependents).
217    pub fn can_unload(&self, name: &str) -> bool {
218        let dependents = self.all_dependents(name);
219
220        for dep_name in &dependents {
221            if let Some(node) = self.nodes.get(dep_name) {
222                if node.loaded {
223                    return false; // Has loaded dependent
224                }
225            }
226        }
227
228        true
229    }
230
231    /// Returns the number of registered modules.
232    pub fn module_count(&self) -> usize {
233        self.nodes.len()
234    }
235
236    /// Returns all module names.
237    pub fn all_modules(&self) -> Vec<String> {
238        self.nodes.iter().map(|e| e.key().clone()).collect()
239    }
240
241    /// Returns information about a specific module.
242    pub fn get_module(&self, name: &str) -> Option<DependencyNode> {
243        self.nodes.get(name).map(|e| e.value().clone())
244    }
245}
246
247impl Default for DependencyGraph {
248    fn default() -> Self {
249        Self::new()
250    }
251}
252
253/// Context for nested module calls.
254#[derive(Debug, Clone)]
255pub struct CallContext {
256    /// The calling module (if any).
257    pub caller: Option<String>,
258    /// The target module.
259    pub target: String,
260    /// Current call depth.
261    pub depth: usize,
262    /// Maximum allowed depth.
263    pub max_depth: usize,
264    /// Trace ID for distributed tracing.
265    pub trace_id: Option<u128>,
266}
267
268impl CallContext {
269    /// Creates a new root call context.
270    pub fn new(target: String) -> Self {
271        Self {
272            caller: None,
273            target,
274            depth: 0,
275            max_depth: 10,
276            trace_id: None,
277        }
278    }
279
280    /// Creates a child context for a nested call.
281    pub fn child(&self, target: String) -> Option<Self> {
282        if self.depth >= self.max_depth {
283            return None; // Max depth exceeded
284        }
285
286        Some(Self {
287            caller: Some(self.target.clone()),
288            target,
289            depth: self.depth + 1,
290            max_depth: self.max_depth,
291            trace_id: self.trace_id,
292        })
293    }
294
295    /// Returns whether this is a root call (not nested).
296    pub fn is_root(&self) -> bool {
297        self.caller.is_none()
298    }
299
300    /// Returns whether this is a nested call.
301    pub fn is_nested(&self) -> bool {
302        self.caller.is_some()
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn test_dependency_graph_basic() {
312        let graph = DependencyGraph::new();
313
314        graph.add_module("A", vec![]);
315        graph.add_module("B", vec![
316            ModuleDependency { name: "A".to_string(), min_version: None, optional: false }
317        ]);
318        graph.add_module("C", vec![
319            ModuleDependency { name: "B".to_string(), min_version: None, optional: false }
320        ]);
321
322        // Load order should be A, B, C
323        let order = graph.load_order().unwrap();
324        assert_eq!(order, vec!["A", "B", "C"]);
325
326        // Unload order should be C, B, A
327        let order = graph.unload_order().unwrap();
328        assert_eq!(order, vec!["C", "B", "A"]);
329    }
330
331    #[test]
332    fn test_dependency_graph_optional() {
333        let graph = DependencyGraph::new();
334
335        graph.add_module("A", vec![]);
336        graph.add_module("B", vec![
337            ModuleDependency { name: "A".to_string(), min_version: None, optional: true }
338        ]);
339
340        // B can load even if A is not loaded (optional dependency)
341        graph.mark_loaded("B");
342        assert!(graph.dependencies_satisfied("B"));
343    }
344
345    #[test]
346    fn test_dependency_graph_cycle_detection() {
347        let graph = DependencyGraph::new();
348
349        graph.add_module("A", vec![
350            ModuleDependency { name: "B".to_string(), min_version: None, optional: false }
351        ]);
352        graph.add_module("B", vec![
353            ModuleDependency { name: "A".to_string(), min_version: None, optional: false }
354        ]);
355
356        // Should detect cycle
357        assert!(graph.has_cycle());
358        assert!(graph.load_order().is_err());
359    }
360
361    #[test]
362    fn test_call_context_depth() {
363        let root = CallContext::new("A".to_string());
364        assert_eq!(root.depth, 0);
365        assert!(root.is_root());
366
367        let child1 = root.child("B".to_string()).unwrap();
368        assert_eq!(child1.depth, 1);
369        assert!(child1.is_nested());
370
371        let child2 = child1.child("C".to_string()).unwrap();
372        assert_eq!(child2.depth, 2);
373
374        // Test max depth
375        let mut ctx = root;
376        for i in 0..10 {
377            if let Some(next) = ctx.child(format!("M{}", i)) {
378                ctx = next;
379            }
380        }
381        // 11th level should fail
382        assert!(ctx.child("M10".to_string()).is_none());
383    }
384
385    #[test]
386    fn test_transitive_dependencies() {
387        let graph = DependencyGraph::new();
388
389        graph.add_module("A", vec![]);
390        graph.add_module("B", vec![
391            ModuleDependency { name: "A".to_string(), min_version: None, optional: false }
392        ]);
393        graph.add_module("C", vec![
394            ModuleDependency { name: "B".to_string(), min_version: None, optional: false }
395        ]);
396
397        // C's transitive dependencies should include A and B
398        let deps = graph.all_dependencies("C");
399        assert!(deps.contains("A"));
400        assert!(deps.contains("B"));
401        assert_eq!(deps.len(), 2);
402    }
403
404    #[test]
405    fn test_can_unload() {
406        let graph = DependencyGraph::new();
407
408        graph.add_module("A", vec![]);
409        graph.add_module("B", vec![
410            ModuleDependency { name: "A".to_string(), min_version: None, optional: false }
411        ]);
412
413        graph.mark_loaded("A");
414        graph.mark_loaded("B");
415
416        // Can't unload A because B depends on it and is loaded
417        assert!(!graph.can_unload("A"));
418
419        // Can unload B
420        assert!(graph.can_unload("B"));
421
422        // After unloading B, can unload A
423        graph.mark_unloaded("B");
424        assert!(graph.can_unload("A"));
425    }
426}