use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct CompileTimeModuleMetadata {
pub name: String,
pub controllers: Vec<String>,
pub providers: Vec<String>,
pub imports: Vec<String>,
pub exports: Vec<String>,
}
impl CompileTimeModuleMetadata {
pub fn new(name: String) -> Self {
Self {
name,
controllers: Vec::new(),
providers: Vec::new(),
imports: Vec::new(),
exports: Vec::new(),
}
}
pub fn with_controller(mut self, controller: String) -> Self {
self.controllers.push(controller);
self
}
pub fn with_controllers(mut self, controllers: Vec<String>) -> Self {
self.controllers.extend(controllers);
self
}
pub fn with_provider(mut self, provider: String) -> Self {
self.providers.push(provider);
self
}
pub fn with_providers(mut self, providers: Vec<String>) -> Self {
self.providers.extend(providers);
self
}
pub fn with_import(mut self, import: String) -> Self {
self.imports.push(import);
self
}
pub fn with_imports(mut self, imports: Vec<String>) -> Self {
self.imports.extend(imports);
self
}
pub fn with_export(mut self, export: String) -> Self {
self.exports.push(export);
self
}
pub fn with_exports(mut self, exports: Vec<String>) -> Self {
self.exports.extend(exports);
self
}
}
#[derive(Debug, Clone)]
pub struct CompileTimeModuleRegistry {
modules: HashMap<String, CompileTimeModuleMetadata>,
dependency_graph: HashMap<String, Vec<String>>,
}
impl CompileTimeModuleRegistry {
pub fn new() -> Self {
Self {
modules: HashMap::new(),
dependency_graph: HashMap::new(),
}
}
pub fn register_module(&mut self, metadata: CompileTimeModuleMetadata) {
let module_name = metadata.name.clone();
let dependencies = metadata.imports.clone();
self.modules.insert(module_name.clone(), metadata);
self.dependency_graph.insert(module_name, dependencies);
}
pub fn all_modules(&self) -> Vec<&CompileTimeModuleMetadata> {
self.modules.values().collect()
}
pub fn find_module(&self, name: &str) -> Option<&CompileTimeModuleMetadata> {
self.modules.get(name)
}
pub fn all_controllers(&self) -> Vec<String> {
self.modules
.values()
.flat_map(|module| module.controllers.iter().cloned())
.collect()
}
pub fn all_providers(&self) -> Vec<String> {
self.modules
.values()
.flat_map(|module| module.providers.iter().cloned())
.collect()
}
pub fn modules_with_controllers(&self) -> Vec<&CompileTimeModuleMetadata> {
self.modules
.values()
.filter(|module| !module.controllers.is_empty())
.collect()
}
pub fn modules_with_providers(&self) -> Vec<&CompileTimeModuleMetadata> {
self.modules
.values()
.filter(|module| !module.providers.is_empty())
.collect()
}
pub fn dependency_graph(&self) -> &HashMap<String, Vec<String>> {
&self.dependency_graph
}
pub fn resolve_dependency_order(&self) -> Result<Vec<&CompileTimeModuleMetadata>, String> {
let mut visited = std::collections::HashSet::new();
let mut temp_visited = std::collections::HashSet::new();
let mut result = Vec::new();
let mut module_names: Vec<_> = self.modules.keys().collect();
module_names.sort();
for module_name in module_names {
if !visited.contains(module_name) {
self.visit_for_topological_sort(
module_name,
&mut visited,
&mut temp_visited,
&mut result,
)?;
}
}
Ok(result)
}
fn visit_for_topological_sort<'a>(
&'a self,
module_name: &str,
visited: &mut std::collections::HashSet<String>,
temp_visited: &mut std::collections::HashSet<String>,
result: &mut Vec<&'a CompileTimeModuleMetadata>,
) -> Result<(), String> {
if temp_visited.contains(module_name) {
return Err(format!("Circular dependency detected involving module '{}'", module_name));
}
if visited.contains(module_name) {
return Ok(());
}
temp_visited.insert(module_name.to_string());
if let Some(dependencies) = self.dependency_graph.get(module_name) {
for dep_name in dependencies {
if !self.modules.contains_key(dep_name) {
return Err(format!(
"Module '{}' depends on '{}' which is not registered",
module_name, dep_name
));
}
self.visit_for_topological_sort(dep_name, visited, temp_visited, result)?;
}
}
temp_visited.remove(module_name);
visited.insert(module_name.to_string());
if let Some(metadata) = self.modules.get(module_name) {
result.push(metadata);
}
Ok(())
}
pub fn module_count(&self) -> usize {
self.modules.len()
}
pub fn is_empty(&self) -> bool {
self.modules.is_empty()
}
}
impl Default for CompileTimeModuleRegistry {
fn default() -> Self {
Self::new()
}
}
use std::sync::{Mutex, OnceLock};
static GLOBAL_MODULE_REGISTRY: OnceLock<Mutex<CompileTimeModuleRegistry>> = OnceLock::new();
pub fn register_module_globally(metadata: CompileTimeModuleMetadata) {
let registry_mutex = GLOBAL_MODULE_REGISTRY.get_or_init(|| Mutex::new(CompileTimeModuleRegistry::new()));
registry_mutex
.lock()
.expect("Global module registry is poisoned")
.register_module(metadata);
}
pub fn get_global_module_registry() -> CompileTimeModuleRegistry {
let registry_mutex = GLOBAL_MODULE_REGISTRY.get_or_init(|| Mutex::new(CompileTimeModuleRegistry::new()));
registry_mutex
.lock()
.expect("Global module registry is poisoned")
.clone()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_metadata_creation() {
let metadata = CompileTimeModuleMetadata::new("UserModule".to_string())
.with_controller("UserController".to_string())
.with_provider("UserService".to_string())
.with_import("AuthModule".to_string())
.with_export("UserService".to_string());
assert_eq!(metadata.name, "UserModule");
assert_eq!(metadata.controllers, vec!["UserController"]);
assert_eq!(metadata.providers, vec!["UserService"]);
assert_eq!(metadata.imports, vec!["AuthModule"]);
assert_eq!(metadata.exports, vec!["UserService"]);
}
#[test]
fn test_module_registry_basic_operations() {
let mut registry = CompileTimeModuleRegistry::new();
let user_module = CompileTimeModuleMetadata::new("UserModule".to_string())
.with_controller("UserController".to_string())
.with_provider("UserService".to_string());
let auth_module = CompileTimeModuleMetadata::new("AuthModule".to_string())
.with_provider("AuthService".to_string());
registry.register_module(user_module);
registry.register_module(auth_module);
assert_eq!(registry.module_count(), 2);
assert!(registry.find_module("UserModule").is_some());
assert!(registry.find_module("AuthModule").is_some());
assert!(registry.find_module("NonExistentModule").is_none());
let controllers = registry.all_controllers();
assert_eq!(controllers.len(), 1);
assert!(controllers.contains(&"UserController".to_string()));
let providers = registry.all_providers();
assert_eq!(providers.len(), 2);
assert!(providers.contains(&"UserService".to_string()));
assert!(providers.contains(&"AuthService".to_string()));
}
#[test]
fn test_dependency_resolution() {
let mut registry = CompileTimeModuleRegistry::new();
let auth_module = CompileTimeModuleMetadata::new("AuthModule".to_string())
.with_provider("AuthService".to_string());
let user_module = CompileTimeModuleMetadata::new("UserModule".to_string())
.with_controller("UserController".to_string())
.with_import("AuthModule".to_string());
registry.register_module(user_module);
registry.register_module(auth_module);
let resolved_order = registry.resolve_dependency_order().unwrap();
let module_names: Vec<_> = resolved_order.iter().map(|m| &m.name).collect();
assert_eq!(module_names.len(), 2);
let auth_index = module_names.iter().position(|&name| name == "AuthModule").unwrap();
let user_index = module_names.iter().position(|&name| name == "UserModule").unwrap();
assert!(auth_index < user_index);
}
#[test]
fn test_circular_dependency_detection() {
let mut registry = CompileTimeModuleRegistry::new();
let module_a = CompileTimeModuleMetadata::new("ModuleA".to_string())
.with_import("ModuleB".to_string());
let module_b = CompileTimeModuleMetadata::new("ModuleB".to_string())
.with_import("ModuleA".to_string());
registry.register_module(module_a);
registry.register_module(module_b);
let result = registry.resolve_dependency_order();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Circular dependency"));
}
#[test]
fn test_missing_dependency_detection() {
let mut registry = CompileTimeModuleRegistry::new();
let user_module = CompileTimeModuleMetadata::new("UserModule".to_string())
.with_import("NonExistentModule".to_string());
registry.register_module(user_module);
let result = registry.resolve_dependency_order();
assert!(result.is_err());
assert!(result.unwrap_err().contains("not registered"));
}
#[test]
fn test_global_registry() {
let metadata = CompileTimeModuleMetadata::new("TestModule".to_string())
.with_controller("TestController".to_string());
register_module_globally(metadata);
let global_registry = get_global_module_registry();
assert!(global_registry.find_module("TestModule").is_some());
}
}