use std::any::TypeId;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::container::binding::{ServiceBinder, ServiceBindings};
use crate::container::ioc_container::IocContainer;
use crate::errors::CoreError;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ModuleId {
name: String,
type_id: TypeId,
}
impl ModuleId {
pub fn of<T: ServiceModule + 'static>() -> Self {
Self {
name: std::any::type_name::<T>().to_string(),
type_id: TypeId::of::<T>(),
}
}
pub fn named(name: &str) -> Self {
Self {
name: name.to_string(),
type_id: TypeId::of::<()>(), }
}
pub fn name(&self) -> &str {
&self.name
}
}
pub trait ServiceModule: Send + Sync
where
Self: 'static,
{
fn id(&self) -> ModuleId
where
Self: Sized,
{
ModuleId::of::<Self>()
}
fn name(&self) -> &str {
std::any::type_name::<Self>()
}
fn description(&self) -> Option<&str> {
None
}
fn version(&self) -> Option<&str> {
None
}
fn configure(&self, services: &mut ServiceBindings) {
let _ = services;
}
fn depends_on(&self) -> Vec<ModuleId> {
vec![]
}
fn initialize(
&self,
container: &IocContainer,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), CoreError>> + Send + '_>>
{
let _ = container; Box::pin(async move { Ok(()) })
}
fn shutdown(
&self,
container: &IocContainer,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), CoreError>> + Send + '_>>
{
let _ = container; Box::pin(async move { Ok(()) })
}
fn is_compatible_with(&self, other_version: &str) -> bool {
let _ = other_version; true
}
fn metadata(&self) -> ModuleMetadata
where
Self: Sized,
{
ModuleMetadata {
id: self.id(),
name: self.name().to_string(),
description: self.description().map(|s| s.to_string()),
version: self.version().map(|s| s.to_string()),
dependencies: self.depends_on(),
}
}
}
#[derive(Debug, Clone)]
pub struct ModuleMetadata {
pub id: ModuleId,
pub name: String,
pub description: Option<String>,
pub version: Option<String>,
pub dependencies: Vec<ModuleId>,
}
#[derive(Debug, Clone)]
pub struct ModuleConfig {
pub auto_initialize: bool,
pub init_timeout: Option<std::time::Duration>,
pub validate_dependencies: bool,
pub parameters: HashMap<String, String>,
}
impl Default for ModuleConfig {
fn default() -> Self {
Self {
auto_initialize: true,
init_timeout: Some(std::time::Duration::from_secs(30)),
validate_dependencies: true,
parameters: HashMap::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ModuleState {
Registered,
Configured,
Initialized,
Failed(String),
Shutdown,
}
#[derive(Debug, Clone)]
pub struct LoadedModule {
pub metadata: ModuleMetadata,
pub config: ModuleConfig,
pub state: ModuleState,
pub load_order: usize,
pub init_duration: Option<std::time::Duration>,
}
pub struct ModuleRegistry {
modules: HashMap<ModuleId, Arc<dyn ServiceModule>>,
loaded_modules: HashMap<ModuleId, LoadedModule>,
dependency_graph: HashMap<ModuleId, Vec<ModuleId>>,
load_order: Vec<ModuleId>,
}
impl ModuleRegistry {
pub fn new() -> Self {
Self {
modules: HashMap::new(),
loaded_modules: HashMap::new(),
dependency_graph: HashMap::new(),
load_order: Vec::new(),
}
}
pub fn register_module<T: ServiceModule + 'static>(
&mut self,
module: T,
config: Option<ModuleConfig>,
) -> Result<(), CoreError> {
let module_arc = Arc::new(module);
let module_id = module_arc.id();
let metadata = module_arc.metadata();
if self.modules.contains_key(&module_id) {
return Err(CoreError::InvalidServiceDescriptor {
message: format!("Module {} is already registered", metadata.name),
});
}
self.modules.insert(module_id.clone(), module_arc);
self.dependency_graph
.insert(module_id.clone(), metadata.dependencies.clone());
let loaded_module = LoadedModule {
metadata,
config: config.unwrap_or_default(),
state: ModuleState::Registered,
load_order: 0, init_duration: None,
};
self.loaded_modules.insert(module_id, loaded_module);
Ok(())
}
pub fn register_modules<T: ServiceModule + 'static>(
&mut self,
modules: Vec<T>,
) -> Result<(), CoreError> {
for module in modules {
self.register_module(module, None)?;
}
Ok(())
}
pub fn calculate_load_order(&mut self) -> Result<Vec<ModuleId>, CoreError> {
let mut visited = HashSet::new();
let mut temp_visited = HashSet::new();
let mut order = Vec::new();
let mut module_ids: Vec<_> = self.modules.keys().cloned().collect();
module_ids.sort_by(|a, b| a.name().cmp(b.name()));
for module_id in module_ids {
if !visited.contains(&module_id) {
self.visit_module_for_ordering(
&module_id,
&mut visited,
&mut temp_visited,
&mut order,
)?;
}
}
self.load_order = order.clone();
for (index, module_id) in order.iter().enumerate() {
if let Some(loaded_module) = self.loaded_modules.get_mut(module_id) {
loaded_module.load_order = index;
}
}
Ok(order)
}
fn visit_module_for_ordering(
&self,
module_id: &ModuleId,
visited: &mut HashSet<ModuleId>,
temp_visited: &mut HashSet<ModuleId>,
order: &mut Vec<ModuleId>,
) -> Result<(), CoreError> {
if temp_visited.contains(module_id) {
return Err(CoreError::InvalidServiceDescriptor {
message: format!(
"Circular dependency detected involving module {}",
module_id.name()
),
});
}
if visited.contains(module_id) {
return Ok(());
}
temp_visited.insert(module_id.clone());
if let Some(dependencies) = self.dependency_graph.get(module_id) {
for dep_id in dependencies {
if !self.modules.contains_key(dep_id) {
return Err(CoreError::ServiceNotFound {
service_type: format!(
"Module dependency {} for module {}",
dep_id.name(),
module_id.name()
),
});
}
self.visit_module_for_ordering(dep_id, visited, temp_visited, order)?;
}
}
temp_visited.remove(module_id);
visited.insert(module_id.clone());
order.push(module_id.clone());
Ok(())
}
pub fn configure_all<T: ServiceBinder>(&mut self, container: &mut T) -> Result<(), CoreError> {
let order = if self.load_order.is_empty() {
self.calculate_load_order()?
} else {
self.load_order.clone()
};
let mut bindings = ServiceBindings::new();
for module_id in &order {
let module = self
.modules
.get(module_id)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!("Module {}", module_id.name()),
})?
.clone();
module.configure(&mut bindings);
if let Some(loaded_module) = self.loaded_modules.get_mut(module_id) {
loaded_module.state = ModuleState::Configured;
}
}
for descriptor in bindings.into_descriptors() {
container.add_service_descriptor(descriptor)?;
}
Ok(())
}
pub async fn initialize_all(&mut self, container: &IocContainer) -> Result<(), CoreError> {
let order = self.load_order.clone();
for module_id in order {
let start_time = std::time::Instant::now();
let module = self
.modules
.get(&module_id)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!("Module {}", module_id.name()),
})?
.clone();
let config = self
.loaded_modules
.get(&module_id)
.map(|m| m.config.clone())
.unwrap_or_default();
let result = if let Some(timeout) = config.init_timeout {
tokio::time::timeout(timeout, module.initialize(container))
.await
.map_err(|_| CoreError::InvalidServiceDescriptor {
message: format!("Module {} initialization timed out", module_id.name()),
})?
} else {
module.initialize(container).await
};
let duration = start_time.elapsed();
if let Some(loaded_module) = self.loaded_modules.get_mut(&module_id) {
loaded_module.init_duration = Some(duration);
loaded_module.state = match result {
Ok(()) => ModuleState::Initialized,
Err(ref e) => ModuleState::Failed(e.to_string()),
};
}
result?;
}
Ok(())
}
pub async fn shutdown_all(&mut self, container: &IocContainer) -> Result<(), CoreError> {
let mut order = self.load_order.clone();
order.reverse();
for module_id in order {
let module = self
.modules
.get(&module_id)
.ok_or_else(|| CoreError::ServiceNotFound {
service_type: format!("Module {}", module_id.name()),
})?
.clone();
if let Err(e) = module.shutdown(container).await {
eprintln!(
"Warning: Module {} shutdown failed: {}",
module_id.name(),
e
);
}
if let Some(loaded_module) = self.loaded_modules.get_mut(&module_id) {
loaded_module.state = ModuleState::Shutdown;
}
}
Ok(())
}
pub fn get_module(&self, module_id: &ModuleId) -> Option<&Arc<dyn ServiceModule>> {
self.modules.get(module_id)
}
pub fn get_loaded_module(&self, module_id: &ModuleId) -> Option<&LoadedModule> {
self.loaded_modules.get(module_id)
}
pub fn get_all_loaded_modules(&self) -> Vec<&LoadedModule> {
self.loaded_modules.values().collect()
}
pub fn is_module_ready(&self, module_id: &ModuleId) -> bool {
self.loaded_modules
.get(module_id)
.map(|m| m.state == ModuleState::Initialized)
.unwrap_or(false)
}
pub fn get_dependency_graph(&self) -> &HashMap<ModuleId, Vec<ModuleId>> {
&self.dependency_graph
}
pub fn get_load_order(&self) -> &[ModuleId] {
&self.load_order
}
pub fn validate_dependencies(&self) -> Result<(), Vec<CoreError>> {
let mut errors = Vec::new();
for (module_id, dependencies) in &self.dependency_graph {
for dep_id in dependencies {
if !self.modules.contains_key(dep_id) {
errors.push(CoreError::ServiceNotFound {
service_type: format!(
"Module dependency {} for module {}",
dep_id.name(),
module_id.name()
),
});
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl Default for ModuleRegistry {
fn default() -> Self {
Self::new()
}
}
pub struct ModularContainerBuilder<T>
where
T: ServiceBinder,
{
registry: ModuleRegistry,
container: T,
}
impl<T> ModularContainerBuilder<T>
where
T: ServiceBinder,
{
pub fn new(container: T) -> Self {
Self {
registry: ModuleRegistry::new(),
container,
}
}
pub fn add_module<M: ServiceModule + 'static>(mut self, module: M) -> Result<Self, CoreError> {
self.registry.register_module(module, None)?;
Ok(self)
}
pub fn add_module_with_config<M: ServiceModule + 'static>(
mut self,
module: M,
config: ModuleConfig,
) -> Result<Self, CoreError> {
self.registry.register_module(module, Some(config))?;
Ok(self)
}
pub fn add_modules<M: ServiceModule + 'static>(
mut self,
modules: Vec<M>,
) -> Result<Self, CoreError> {
self.registry.register_modules(modules)?;
Ok(self)
}
pub fn build(mut self) -> Result<(T, ModuleRegistry), CoreError> {
self.registry
.validate_dependencies()
.map_err(|errors| errors.into_iter().next().unwrap())?;
self.registry.configure_all(&mut self.container)?;
Ok((self.container, self.registry))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::container::ioc_container::IocContainer;
struct CoreModule;
impl ServiceModule for CoreModule {
fn name(&self) -> &str {
"Core Module"
}
fn description(&self) -> Option<&str> {
Some("Core application services")
}
fn configure(&self, _services: &mut ServiceBindings) {
}
}
struct AuthModule;
impl ServiceModule for AuthModule {
fn name(&self) -> &str {
"Auth Module"
}
fn depends_on(&self) -> Vec<ModuleId> {
vec![ModuleId::of::<CoreModule>()]
}
fn configure(&self, _services: &mut ServiceBindings) {
}
}
struct ApiModule;
impl ServiceModule for ApiModule {
fn name(&self) -> &str {
"API Module"
}
fn depends_on(&self) -> Vec<ModuleId> {
vec![ModuleId::of::<AuthModule>(), ModuleId::of::<CoreModule>()]
}
fn configure(&self, _services: &mut ServiceBindings) {
}
}
#[test]
fn test_module_registration() {
let mut registry = ModuleRegistry::new();
registry.register_module(CoreModule, None).unwrap();
registry.register_module(AuthModule, None).unwrap();
assert_eq!(registry.modules.len(), 2);
assert_eq!(registry.loaded_modules.len(), 2);
}
#[test]
fn test_dependency_ordering() {
let mut registry = ModuleRegistry::new();
registry.register_module(ApiModule, None).unwrap();
registry.register_module(CoreModule, None).unwrap();
registry.register_module(AuthModule, None).unwrap();
let order = registry.calculate_load_order().unwrap();
let order_names: Vec<String> = order.iter().map(|id| id.name().to_string()).collect();
assert_eq!(order_names[0], std::any::type_name::<CoreModule>());
assert_eq!(order_names[1], std::any::type_name::<AuthModule>());
assert_eq!(order_names[2], std::any::type_name::<ApiModule>());
}
#[test]
fn test_circular_dependency_detection() {
struct Module1;
struct Module2;
impl ServiceModule for Module1 {
fn depends_on(&self) -> Vec<ModuleId> {
vec![ModuleId::of::<Module2>()]
}
fn configure(&self, _services: &mut ServiceBindings) {}
}
impl ServiceModule for Module2 {
fn depends_on(&self) -> Vec<ModuleId> {
vec![ModuleId::of::<Module1>()] }
fn configure(&self, _services: &mut ServiceBindings) {}
}
let mut registry = ModuleRegistry::new();
registry.register_module(Module1, None).unwrap();
registry.register_module(Module2, None).unwrap();
let result = registry.calculate_load_order();
assert!(result.is_err());
}
#[test]
fn test_missing_dependency_validation() {
struct ModuleWithMissingDep;
impl ServiceModule for ModuleWithMissingDep {
fn depends_on(&self) -> Vec<ModuleId> {
vec![ModuleId::named("NonExistentModule")]
}
fn configure(&self, _services: &mut ServiceBindings) {}
}
let mut registry = ModuleRegistry::new();
registry
.register_module(ModuleWithMissingDep, None)
.unwrap();
let result = registry.calculate_load_order();
assert!(result.is_err());
}
#[tokio::test]
async fn test_modular_container_builder() {
let container = IocContainer::new();
let builder = ModularContainerBuilder::new(container);
let result = builder
.add_module(CoreModule)
.unwrap()
.add_module(AuthModule)
.unwrap()
.add_module(ApiModule)
.unwrap()
.build();
assert!(result.is_ok());
let (_container, registry) = result.unwrap();
assert_eq!(registry.get_load_order().len(), 3);
}
#[test]
fn test_module_service_configuration() {
use crate::container::ioc_builder::IocContainerBuilder;
struct TestModule;
#[derive(Default)]
struct TestService {
#[allow(dead_code)]
pub name: String,
}
impl ServiceModule for TestModule {
fn configure(&self, services: &mut ServiceBindings) {
services.bind::<TestService, TestService>();
}
}
let mut registry = ModuleRegistry::new();
registry.register_module(TestModule, None).unwrap();
let mut container_builder = IocContainerBuilder::new();
registry.configure_all(&mut container_builder).unwrap();
let container = container_builder.build().unwrap();
let service = container.resolve::<TestService>();
assert!(service.is_ok());
}
}