Skip to main content

nidus_core/module/
graph.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{Module, NidusError, Result};
4
5use super::ModuleDefinition;
6
7/// Validated graph of module definitions.
8#[derive(Debug)]
9pub struct ModuleGraph {
10    modules: BTreeMap<String, ModuleDefinition>,
11}
12
13impl ModuleGraph {
14    /// Builds and validates a module graph by recursively following typed imports.
15    pub fn from_root<M: Module>() -> Result<Self> {
16        Self::from_root_and_modules::<M, _>([])
17    }
18
19    /// Builds and validates a module graph from a root module plus explicit definitions.
20    pub fn from_root_and_modules<M, I>(modules: I) -> Result<Self>
21    where
22        M: Module,
23        I: IntoIterator<Item = ModuleDefinition>,
24    {
25        let mut definitions = Vec::new();
26        collect_recursive(M::definition(), &mut definitions, &mut BTreeSet::new());
27        for module in modules {
28            collect_recursive(module, &mut definitions, &mut BTreeSet::new());
29        }
30        Self::from_modules(definitions)
31    }
32
33    /// Builds and validates a module graph.
34    pub fn from_modules(modules: impl IntoIterator<Item = ModuleDefinition>) -> Result<Self> {
35        let span = tracing::info_span!("module.graph.validate");
36        let _entered = span.enter();
37        let mut registered = BTreeMap::new();
38        for module in modules {
39            let name = module.name.clone();
40            if registered.insert(name.clone(), module).is_some() {
41                return Err(NidusError::DuplicateModule { module: name });
42            }
43        }
44        let graph = Self {
45            modules: registered,
46        };
47        tracing::debug!(
48            module_count = graph.modules.len(),
49            "validating module graph"
50        );
51        for module in graph.modules.values() {
52            tracing::debug!(
53                module = %module.name,
54                imports = ?module.imports,
55                providers = ?module.providers,
56                controllers = ?module.controllers,
57                exports = ?module.exports,
58                "module graph node"
59            );
60        }
61        graph.validate_local_imports_unique()?;
62        graph.validate_imports_exist()?;
63        graph.validate_acyclic()?;
64        graph.validate_local_providers_unique()?;
65        graph.validate_local_controllers_unique()?;
66        graph.validate_providers_and_controllers_disjoint()?;
67        graph.validate_exports_unique()?;
68        graph.validate_exports_are_local()?;
69        graph.validate_local_providers_do_not_conflict_with_imports()?;
70        graph.validate_visible_providers_unambiguous()?;
71        tracing::debug!(module_count = graph.modules.len(), "module graph validated");
72        Ok(graph)
73    }
74
75    /// Returns a module definition by name.
76    pub fn get(&self, name: &str) -> Option<&ModuleDefinition> {
77        self.modules.get(name)
78    }
79
80    /// Returns validated module definitions in deterministic name order.
81    pub fn modules(&self) -> impl Iterator<Item = &ModuleDefinition> {
82        self.modules.values()
83    }
84
85    fn validate_local_imports_unique(&self) -> Result<()> {
86        for module in self.modules.values() {
87            let mut seen = BTreeSet::new();
88            for import in &module.imports {
89                if !seen.insert(import) {
90                    return Err(NidusError::DuplicateModuleImport {
91                        module: module.name.clone(),
92                        import: import.clone(),
93                    });
94                }
95            }
96        }
97        Ok(())
98    }
99
100    fn validate_imports_exist(&self) -> Result<()> {
101        for module in self.modules.values() {
102            for import in &module.imports {
103                if !self.modules.contains_key(import) {
104                    return Err(NidusError::MissingModuleImport {
105                        module: module.name.clone(),
106                        import: import.clone(),
107                    });
108                }
109            }
110        }
111        Ok(())
112    }
113
114    fn validate_acyclic(&self) -> Result<()> {
115        let mut visiting = BTreeSet::new();
116        let mut visited = BTreeSet::new();
117        let mut stack = Vec::new();
118
119        for name in self.modules.keys() {
120            self.visit(name, &mut visiting, &mut visited, &mut stack)?;
121        }
122        Ok(())
123    }
124
125    fn validate_local_providers_unique(&self) -> Result<()> {
126        for module in self.modules.values() {
127            let mut seen = BTreeSet::new();
128            for provider in &module.providers {
129                if !seen.insert(provider) {
130                    return Err(NidusError::DuplicateModuleProvider {
131                        module: module.name.clone(),
132                        provider: provider.clone(),
133                    });
134                }
135            }
136        }
137        Ok(())
138    }
139
140    fn validate_local_controllers_unique(&self) -> Result<()> {
141        for module in self.modules.values() {
142            let mut seen = BTreeSet::new();
143            for controller in &module.controllers {
144                if !seen.insert(controller) {
145                    return Err(NidusError::DuplicateModuleController {
146                        module: module.name.clone(),
147                        controller: controller.clone(),
148                    });
149                }
150            }
151        }
152        Ok(())
153    }
154
155    fn validate_providers_and_controllers_disjoint(&self) -> Result<()> {
156        for module in self.modules.values() {
157            let providers = module.providers.iter().collect::<BTreeSet<_>>();
158            for controller in &module.controllers {
159                if providers.contains(controller) {
160                    return Err(NidusError::ModuleProviderControllerConflict {
161                        module: module.name.clone(),
162                        type_name: controller.clone(),
163                    });
164                }
165            }
166        }
167        Ok(())
168    }
169
170    fn validate_exports_unique(&self) -> Result<()> {
171        for module in self.modules.values() {
172            let mut seen = BTreeSet::new();
173            for export in &module.exports {
174                if !seen.insert(export) {
175                    return Err(NidusError::DuplicateModuleExport {
176                        module: module.name.clone(),
177                        provider: export.clone(),
178                    });
179                }
180            }
181        }
182        Ok(())
183    }
184
185    fn validate_exports_are_local(&self) -> Result<()> {
186        for module in self.modules.values() {
187            let providers = module.providers.iter().collect::<BTreeSet<_>>();
188            for export in &module.exports {
189                if !providers.contains(export) {
190                    return Err(NidusError::MissingProviderExport {
191                        module: module.name.clone(),
192                        provider: export.clone(),
193                    });
194                }
195            }
196        }
197        Ok(())
198    }
199
200    fn validate_local_providers_do_not_conflict_with_imports(&self) -> Result<()> {
201        for module in self.modules.values() {
202            let local_providers = module.providers.iter().collect::<BTreeSet<_>>();
203            for import in &module.imports {
204                let imported = self.modules.get(import).expect("imports were validated");
205                for export in &imported.exports {
206                    if local_providers.contains(export) {
207                        return Err(NidusError::ProviderVisibilityConflict {
208                            module: module.name.clone(),
209                            provider: export.clone(),
210                            import: import.clone(),
211                        });
212                    }
213                }
214            }
215        }
216        Ok(())
217    }
218
219    fn validate_visible_providers_unambiguous(&self) -> Result<()> {
220        for module in self.modules.values() {
221            let mut visible_exports = BTreeMap::<&str, Vec<&str>>::new();
222            for import in &module.imports {
223                let imported = self.modules.get(import).expect("imports were validated");
224                for export in &imported.exports {
225                    visible_exports
226                        .entry(export.as_str())
227                        .or_default()
228                        .push(import.as_str());
229                }
230            }
231
232            for (provider, imports) in visible_exports {
233                if imports.len() > 1 {
234                    return Err(NidusError::AmbiguousProvider {
235                        module: module.name.clone(),
236                        provider: provider.to_owned(),
237                        imports: imports.into_iter().map(str::to_owned).collect(),
238                    });
239                }
240            }
241        }
242        Ok(())
243    }
244
245    fn visit(
246        &self,
247        name: &str,
248        visiting: &mut BTreeSet<String>,
249        visited: &mut BTreeSet<String>,
250        stack: &mut Vec<String>,
251    ) -> Result<()> {
252        if visited.contains(name) {
253            return Ok(());
254        }
255
256        if visiting.contains(name) {
257            let cycle_start = stack.iter().position(|item| item == name).unwrap_or(0);
258            let mut cycle = stack[cycle_start..].to_vec();
259            cycle.push(name.to_owned());
260            return Err(NidusError::CircularModuleImport { cycle });
261        }
262
263        visiting.insert(name.to_owned());
264        stack.push(name.to_owned());
265        if let Some(module) = self.modules.get(name) {
266            for import in &module.imports {
267                self.visit(import, visiting, visited, stack)?;
268            }
269        }
270        stack.pop();
271        visiting.remove(name);
272        visited.insert(name.to_owned());
273        Ok(())
274    }
275}
276
277fn collect_recursive(
278    module: ModuleDefinition,
279    definitions: &mut Vec<ModuleDefinition>,
280    seen: &mut BTreeSet<String>,
281) {
282    if !seen.insert(module.name().to_owned()) {
283        return;
284    }
285
286    for import in module.import_factories() {
287        collect_recursive(import(), definitions, seen);
288    }
289
290    definitions.push(module);
291}