nidus_core/module/
graph.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{Module, NidusError, Result};
4
5use super::ModuleDefinition;
6
7#[derive(Debug)]
9pub struct ModuleGraph {
10 modules: BTreeMap<String, ModuleDefinition>,
11}
12
13impl ModuleGraph {
14 pub fn from_root<M: Module>() -> Result<Self> {
16 Self::from_root_and_modules::<M, _>([])
17 }
18
19 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 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 pub fn get(&self, name: &str) -> Option<&ModuleDefinition> {
77 self.modules.get(name)
78 }
79
80 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}