nidus_core/module/
graph.rs1use std::collections::{BTreeMap, BTreeSet, btree_map::Entry};
2
3use crate::{Module, NidusError, Result};
4
5use super::ModuleDefinition;
6
7enum VisibleProvider<'a> {
8 Unique(&'a str),
9 Ambiguous(Vec<&'a str>),
10}
11
12enum NameLookup<'a> {
13 Empty,
14 One(&'a str),
15 Many(BTreeSet<&'a str>),
16}
17
18impl<'a> NameLookup<'a> {
19 fn new(names: &'a [String]) -> Self {
20 match names {
21 [] => Self::Empty,
22 [name] => Self::One(name),
23 names => Self::Many(names.iter().map(String::as_str).collect()),
24 }
25 }
26
27 fn contains(&self, name: &str) -> bool {
28 match self {
29 Self::Empty => false,
30 Self::One(only) => *only == name,
31 Self::Many(names) => names.contains(name),
32 }
33 }
34}
35
36#[derive(Debug)]
38pub struct ModuleGraph {
39 modules: BTreeMap<String, ModuleDefinition>,
40}
41
42impl ModuleGraph {
43 pub fn from_root<M: Module>() -> Result<Self> {
45 Self::from_root_and_modules::<M, _>([])
46 }
47
48 pub fn from_root_and_modules<M, I>(modules: I) -> Result<Self>
50 where
51 M: Module,
52 I: IntoIterator<Item = ModuleDefinition>,
53 {
54 let mut definitions = Vec::new();
55 collect_recursive(M::definition(), &mut definitions, &mut BTreeSet::new());
56 for module in modules {
57 collect_recursive(module, &mut definitions, &mut BTreeSet::new());
58 }
59 Self::from_modules(definitions)
60 }
61
62 pub fn from_modules(modules: impl IntoIterator<Item = ModuleDefinition>) -> Result<Self> {
64 let span = tracing::info_span!("module.graph.validate");
65 let _entered = span.enter();
66 let mut registered = BTreeMap::new();
67 for module in modules {
68 let name = module.name.clone();
69 match registered.entry(name) {
70 Entry::Vacant(entry) => {
71 entry.insert(module);
72 }
73 Entry::Occupied(entry) => {
74 return Err(NidusError::DuplicateModule {
75 module: entry.key().clone(),
76 });
77 }
78 }
79 }
80 let graph = Self {
81 modules: registered,
82 };
83 tracing::debug!(
84 module_count = graph.modules.len(),
85 "validating module graph"
86 );
87 for module in graph.modules.values() {
88 tracing::debug!(
89 module = %module.name,
90 imports = ?module.imports,
91 providers = ?module.providers,
92 controllers = ?module.controllers,
93 exports = ?module.exports,
94 "module graph node"
95 );
96 }
97 graph.validate_local_imports_unique()?;
98 graph.validate_imports_exist()?;
99 graph.validate_acyclic()?;
100 graph.validate_local_providers_unique()?;
101 graph.validate_local_controllers_unique()?;
102 graph.validate_providers_and_controllers_disjoint()?;
103 graph.validate_exports_unique()?;
104 graph.validate_exports_are_local()?;
105 graph.validate_local_providers_do_not_conflict_with_imports()?;
106 graph.validate_visible_providers_unambiguous()?;
107 tracing::debug!(module_count = graph.modules.len(), "module graph validated");
108 Ok(graph)
109 }
110
111 pub fn get(&self, name: &str) -> Option<&ModuleDefinition> {
113 self.modules.get(name)
114 }
115
116 pub fn modules(&self) -> impl Iterator<Item = &ModuleDefinition> {
118 self.modules.values()
119 }
120
121 fn validate_local_imports_unique(&self) -> Result<()> {
122 for module in self.modules.values() {
123 if let Some(import) = first_duplicate(&module.imports) {
124 return Err(NidusError::DuplicateModuleImport {
125 module: module.name.clone(),
126 import: import.to_owned(),
127 });
128 }
129 }
130 Ok(())
131 }
132
133 fn validate_imports_exist(&self) -> Result<()> {
134 for module in self.modules.values() {
135 for import in &module.imports {
136 if !self.modules.contains_key(import) {
137 return Err(NidusError::MissingModuleImport {
138 module: module.name.clone(),
139 import: import.clone(),
140 });
141 }
142 }
143 }
144 Ok(())
145 }
146
147 fn validate_acyclic(&self) -> Result<()> {
148 let mut visiting = BTreeSet::new();
149 let mut visited = BTreeSet::new();
150 let mut stack = Vec::new();
151
152 for name in self.modules.keys() {
153 self.visit(name, &mut visiting, &mut visited, &mut stack)?;
154 }
155 Ok(())
156 }
157
158 fn validate_local_providers_unique(&self) -> Result<()> {
159 for module in self.modules.values() {
160 if let Some(provider) = first_duplicate(&module.providers) {
161 return Err(NidusError::DuplicateModuleProvider {
162 module: module.name.clone(),
163 provider: provider.to_owned(),
164 });
165 }
166 }
167 Ok(())
168 }
169
170 fn validate_local_controllers_unique(&self) -> Result<()> {
171 for module in self.modules.values() {
172 if let Some(controller) = first_duplicate(&module.controllers) {
173 return Err(NidusError::DuplicateModuleController {
174 module: module.name.clone(),
175 controller: controller.to_owned(),
176 });
177 }
178 }
179 Ok(())
180 }
181
182 fn validate_providers_and_controllers_disjoint(&self) -> Result<()> {
183 for module in self.modules.values() {
184 if module.controllers.is_empty() {
185 continue;
186 }
187 let providers = NameLookup::new(&module.providers);
188 for controller in &module.controllers {
189 if providers.contains(controller) {
190 return Err(NidusError::ModuleProviderControllerConflict {
191 module: module.name.clone(),
192 type_name: controller.clone(),
193 });
194 }
195 }
196 }
197 Ok(())
198 }
199
200 fn validate_exports_unique(&self) -> Result<()> {
201 for module in self.modules.values() {
202 if let Some(export) = first_duplicate(&module.exports) {
203 return Err(NidusError::DuplicateModuleExport {
204 module: module.name.clone(),
205 provider: export.to_owned(),
206 });
207 }
208 }
209 Ok(())
210 }
211
212 fn validate_exports_are_local(&self) -> Result<()> {
213 for module in self.modules.values() {
214 if module.exports.is_empty() {
215 continue;
216 }
217 let providers = NameLookup::new(&module.providers);
218 for export in &module.exports {
219 if !providers.contains(export) {
220 return Err(NidusError::MissingProviderExport {
221 module: module.name.clone(),
222 provider: export.clone(),
223 });
224 }
225 }
226 }
227 Ok(())
228 }
229
230 fn validate_local_providers_do_not_conflict_with_imports(&self) -> Result<()> {
231 for module in self.modules.values() {
232 if module.imports.is_empty() {
233 continue;
234 }
235 let local_providers = NameLookup::new(&module.providers);
236 for import in &module.imports {
237 let imported = self.modules.get(import).expect("imports were validated");
238 for export in &imported.exports {
239 if local_providers.contains(export) {
240 return Err(NidusError::ProviderVisibilityConflict {
241 module: module.name.clone(),
242 provider: export.clone(),
243 import: import.clone(),
244 });
245 }
246 }
247 }
248 }
249 Ok(())
250 }
251
252 fn validate_visible_providers_unambiguous(&self) -> Result<()> {
253 for module in self.modules.values() {
254 let mut visible_exports = BTreeMap::<&str, VisibleProvider<'_>>::new();
255 for import in &module.imports {
256 let imported = self.modules.get(import).expect("imports were validated");
257 for export in &imported.exports {
258 match visible_exports.entry(export.as_str()) {
259 Entry::Vacant(entry) => {
260 entry.insert(VisibleProvider::Unique(import));
261 }
262 Entry::Occupied(mut entry) => {
263 let visible_provider = entry.get_mut();
264 match visible_provider {
265 VisibleProvider::Unique(first_import) => {
266 *visible_provider =
267 VisibleProvider::Ambiguous(vec![*first_import, import]);
268 }
269 VisibleProvider::Ambiguous(imports) => imports.push(import),
270 }
271 }
272 }
273 }
274 }
275
276 for (provider, visibility) in visible_exports {
277 if let VisibleProvider::Ambiguous(imports) = visibility {
278 return Err(NidusError::AmbiguousProvider {
279 module: module.name.clone(),
280 provider: provider.to_owned(),
281 imports: imports.into_iter().map(str::to_owned).collect(),
282 });
283 }
284 }
285 }
286 Ok(())
287 }
288
289 fn visit<'a>(
290 &'a self,
291 name: &'a str,
292 visiting: &mut BTreeSet<&'a str>,
293 visited: &mut BTreeSet<&'a str>,
294 stack: &mut Vec<&'a str>,
295 ) -> Result<()> {
296 if visited.contains(name) {
297 return Ok(());
298 }
299
300 if visiting.contains(name) {
301 let cycle_start = stack.iter().position(|item| *item == name).unwrap_or(0);
302 let mut cycle = stack[cycle_start..]
303 .iter()
304 .map(|name| (*name).to_owned())
305 .collect::<Vec<_>>();
306 cycle.push(name.to_owned());
307 return Err(NidusError::CircularModuleImport { cycle });
308 }
309
310 visiting.insert(name);
311 stack.push(name);
312 if let Some(module) = self.modules.get(name) {
313 for import in &module.imports {
314 self.visit(import, visiting, visited, stack)?;
315 }
316 }
317 stack.pop();
318 visiting.remove(name);
319 visited.insert(name);
320 Ok(())
321 }
322}
323
324fn first_duplicate(names: &[String]) -> Option<&str> {
325 if names.len() < 2 {
326 return None;
327 }
328
329 let mut seen = BTreeSet::new();
330 names
331 .iter()
332 .find(|name| !seen.insert(name.as_str()))
333 .map(String::as_str)
334}
335
336fn collect_recursive(
337 module: ModuleDefinition,
338 definitions: &mut Vec<ModuleDefinition>,
339 seen: &mut BTreeSet<String>,
340) {
341 if !seen.insert(module.name().to_owned()) {
342 return;
343 }
344
345 for import in module.import_factories() {
346 collect_recursive(import(), definitions, seen);
347 }
348
349 definitions.push(module);
350}