use crate::{Container, ControllerRegistration, Error, Guard, ProviderRegistration, Router};
use std::any::TypeId;
use std::collections::HashSet;
use std::sync::Arc;
#[derive(Clone)]
pub struct GuardRegistration {
pub type_id: TypeId,
pub type_name: &'static str,
pub factory: fn(&Container) -> Result<Arc<dyn Guard>, Error>,
}
impl std::fmt::Debug for GuardRegistration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GuardRegistration")
.field("type_id", &self.type_id)
.field("type_name", &self.type_name)
.finish()
}
}
#[derive(Debug, Clone, Default)]
pub struct ModuleMetadata {
pub global: bool,
pub name: Option<String>,
pub version: Option<String>,
pub description: Option<String>,
}
impl ModuleMetadata {
pub fn new() -> Self {
Self::default()
}
pub fn global(mut self) -> Self {
self.global = true;
self
}
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
pub fn version(mut self, version: &str) -> Self {
self.version = Some(version.to_string());
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_string());
self
}
}
pub trait ModuleExt: Send + Sync + 'static {
fn module_type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self>()
}
fn dedup_key(&self) -> Option<std::any::TypeId> {
Some(self.module_type_id())
}
fn providers(&self) -> Vec<ProviderRegistration> {
vec![]
}
fn controllers(&self) -> Vec<ControllerRegistration> {
vec![]
}
fn guards(&self) -> Vec<GuardRegistration> {
vec![]
}
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![]
}
fn exports(&self) -> Vec<TypeId> {
vec![]
}
fn re_exports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::default()
}
fn on_module_init(&self, _container: &Container) -> Result<(), Error> {
Ok(())
}
fn on_module_destroy(&self) -> Result<(), Error> {
Ok(())
}
}
#[derive(Clone)]
pub struct DynamicModule {
pub name: String,
providers: Vec<ProviderRegistration>,
controllers: Vec<ControllerRegistration>,
guards: Vec<GuardRegistration>,
imports: Vec<Arc<dyn ModuleExt>>,
exports: Vec<TypeId>,
re_exports: Vec<Arc<dyn ModuleExt>>,
metadata: ModuleMetadata,
export_all: bool,
}
impl DynamicModule {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
providers: vec![],
controllers: vec![],
guards: vec![],
imports: vec![],
exports: vec![],
re_exports: vec![],
metadata: ModuleMetadata::default().name(name),
export_all: false,
}
}
pub fn with_provider(mut self, provider: ProviderRegistration) -> Self {
self.providers.push(provider);
self
}
pub fn with_providers(mut self, providers: Vec<ProviderRegistration>) -> Self {
self.providers.extend(providers);
self
}
pub fn with_controller(mut self, controller: ControllerRegistration) -> Self {
self.controllers.push(controller);
self
}
pub fn with_controllers(mut self, controllers: Vec<ControllerRegistration>) -> Self {
self.controllers.extend(controllers);
self
}
pub fn with_guard(mut self, guard: GuardRegistration) -> Self {
self.guards.push(guard);
self
}
pub fn with_guards(mut self, guards: Vec<GuardRegistration>) -> Self {
self.guards.extend(guards);
self
}
pub fn import<M: ModuleExt + Clone + 'static>(mut self, module: M) -> Self {
self.imports.push(Arc::new(module));
self
}
pub fn import_boxed(mut self, module: Box<dyn ModuleExt>) -> Self {
self.imports.push(Arc::from(module));
self
}
pub fn export<T: 'static>(mut self) -> Self {
self.exports.push(TypeId::of::<T>());
self
}
pub fn export_id(mut self, type_id: TypeId) -> Self {
self.exports.push(type_id);
self
}
pub fn export_all(mut self) -> Self {
self.export_all = true;
self
}
pub fn re_export<M: ModuleExt + Clone + 'static>(mut self, module: M) -> Self {
self.re_exports.push(Arc::new(module));
self
}
pub fn global(mut self) -> Self {
self.metadata.global = true;
self
}
pub fn with_metadata(mut self, metadata: ModuleMetadata) -> Self {
self.metadata = metadata;
self
}
}
impl ModuleExt for DynamicModule {
fn dedup_key(&self) -> Option<std::any::TypeId> {
None
}
fn providers(&self) -> Vec<ProviderRegistration> {
self.providers.clone()
}
fn controllers(&self) -> Vec<ControllerRegistration> {
self.controllers.clone()
}
fn guards(&self) -> Vec<GuardRegistration> {
self.guards.clone()
}
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
self.imports
.iter()
.map(|m| Box::new(ArcModule(m.clone())) as Box<dyn ModuleExt>)
.collect()
}
fn exports(&self) -> Vec<TypeId> {
if self.export_all {
self.providers.iter().map(|p| p.type_id).collect()
} else {
self.exports.clone()
}
}
fn re_exports(&self) -> Vec<Box<dyn ModuleExt>> {
self.re_exports
.iter()
.map(|m| Box::new(ArcModule(m.clone())) as Box<dyn ModuleExt>)
.collect()
}
fn metadata(&self) -> ModuleMetadata {
self.metadata.clone()
}
}
struct ArcModule(Arc<dyn ModuleExt>);
impl ModuleExt for ArcModule {
fn module_type_id(&self) -> std::any::TypeId {
self.0.module_type_id()
}
fn dedup_key(&self) -> Option<std::any::TypeId> {
self.0.dedup_key()
}
fn providers(&self) -> Vec<ProviderRegistration> {
self.0.providers()
}
fn controllers(&self) -> Vec<ControllerRegistration> {
self.0.controllers()
}
fn guards(&self) -> Vec<GuardRegistration> {
self.0.guards()
}
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
self.0.imports()
}
fn exports(&self) -> Vec<TypeId> {
self.0.exports()
}
fn re_exports(&self) -> Vec<Box<dyn ModuleExt>> {
self.0.re_exports()
}
fn metadata(&self) -> ModuleMetadata {
self.0.metadata()
}
}
pub fn collect_all_exports(module: &dyn ModuleExt) -> HashSet<TypeId> {
let mut exports = HashSet::new();
for type_id in module.exports() {
exports.insert(type_id);
}
for re_export in module.re_exports() {
for type_id in collect_all_exports(re_export.as_ref()) {
exports.insert(type_id);
}
}
exports
}
#[derive(Default)]
pub struct ModuleRegistry {
registered: HashSet<TypeId>,
global_modules: Vec<Arc<dyn ModuleExt>>,
guards: Vec<Arc<dyn Guard>>,
}
impl ModuleRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn is_registered<M: 'static>(&self) -> bool {
self.registered.contains(&TypeId::of::<M>())
}
pub fn mark_registered<M: 'static>(&mut self) {
self.registered.insert(TypeId::of::<M>());
}
pub fn register_module(
&mut self,
container: &Container,
router: &mut Router,
module: &dyn ModuleExt,
) -> Result<(), Error> {
let module_name = module
.metadata()
.name
.unwrap_or_else(|| "UnnamedModule".to_string());
if let Some(module_id) = module.dedup_key()
&& !self.registered.insert(module_id)
{
tracing::debug!(
module = %module_name,
"Module already registered, skipping"
);
return Ok(());
}
tracing::debug!(module = %module_name, "Registering module");
for imported in module.imports() {
self.register_module(container, router, imported.as_ref())?;
}
let globals: Vec<Arc<dyn ModuleExt>> = self.global_modules.clone();
for global in globals {
self.register_module(container, router, global.as_ref())?;
}
for provider_reg in module.providers() {
(provider_reg.register_fn)(container);
tracing::trace!(
module = %module_name,
provider = provider_reg.type_name,
"Provider registered"
);
}
for guard_reg in module.guards() {
match (guard_reg.factory)(container) {
Ok(guard) => {
self.guards.push(guard);
tracing::trace!(
module = %module_name,
guard = guard_reg.type_name,
"Guard registered"
);
}
Err(e) => {
tracing::warn!(
module = %module_name,
guard = guard_reg.type_name,
error = %e,
"Failed to register guard"
);
}
}
}
for controller_reg in module.controllers() {
match (controller_reg.factory)(container) {
Ok(controller_instance) => {
if let Err(e) =
(controller_reg.route_registrar)(container, router, controller_instance)
{
tracing::warn!(
module = %module_name,
controller = controller_reg.type_name,
error = %e,
"Failed to register controller routes"
);
} else {
tracing::trace!(
module = %module_name,
controller = controller_reg.type_name,
base_path = controller_reg.base_path,
"Controller registered"
);
}
}
Err(e) => {
tracing::warn!(
module = %module_name,
controller = controller_reg.type_name,
error = %e,
"Failed to instantiate controller"
);
}
}
}
module.on_module_init(container)?;
tracing::debug!(module = %module_name, "Module registration complete");
Ok(())
}
pub fn add_global_module<M: ModuleExt + 'static>(&mut self, module: M) {
self.global_modules.push(Arc::new(module));
}
pub fn add_global_module_boxed(&mut self, module: Box<dyn ModuleExt>) {
self.global_modules.push(Arc::from(module));
}
pub fn guards(&self) -> &[Arc<dyn Guard>] {
&self.guards
}
}
pub struct ModuleBuilder {
inner: DynamicModule,
}
impl ModuleBuilder {
pub fn new(name: &str) -> Self {
Self {
inner: DynamicModule::new(name),
}
}
pub fn provider(mut self, provider: ProviderRegistration) -> Self {
self.inner = self.inner.with_provider(provider);
self
}
pub fn controller(mut self, controller: ControllerRegistration) -> Self {
self.inner = self.inner.with_controller(controller);
self
}
pub fn guard(mut self, guard: GuardRegistration) -> Self {
self.inner = self.inner.with_guard(guard);
self
}
pub fn import<M: ModuleExt + Clone + 'static>(mut self, module: M) -> Self {
self.inner = self.inner.import(module);
self
}
pub fn export<T: 'static>(mut self) -> Self {
self.inner = self.inner.export::<T>();
self
}
pub fn export_all(mut self) -> Self {
self.inner = self.inner.export_all();
self
}
pub fn re_export<M: ModuleExt + Clone + 'static>(mut self, module: M) -> Self {
self.inner = self.inner.re_export(module);
self
}
pub fn global(mut self) -> Self {
self.inner = self.inner.global();
self
}
pub fn build(self) -> DynamicModule {
self.inner
}
}
#[macro_export]
macro_rules! provider_registration {
($type:ty, $factory:expr) => {
$crate::ProviderRegistration {
type_id: std::any::TypeId::of::<$type>(),
type_name: std::any::type_name::<$type>(),
register_fn: |container| {
let instance: $type = $factory;
let instance = ::std::sync::Arc::new(instance);
if let Some(__armature_lifecycle) = container.lifecycle_manager() {
$crate::__armature_register_lifecycle_hooks!(
&__armature_lifecycle,
std::any::type_name::<$type>(),
instance
);
}
container.register_by_id(
std::any::TypeId::of::<$type>(),
instance as ::std::sync::Arc<dyn ::std::any::Any + Send + Sync>,
);
},
}
};
}
#[macro_export]
macro_rules! guard_registration {
($type:ty, $factory:expr) => {
$crate::module::GuardRegistration {
type_id: std::any::TypeId::of::<$type>(),
type_name: std::any::type_name::<$type>(),
factory: |_container| {
let guard = $factory;
Ok(std::sync::Arc::new(guard) as std::sync::Arc<dyn $crate::Guard>)
},
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(dead_code)]
#[derive(Clone)]
struct TestModule;
impl ModuleExt for TestModule {
fn providers(&self) -> Vec<ProviderRegistration> {
vec![]
}
fn controllers(&self) -> Vec<ControllerRegistration> {
vec![]
}
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![]
}
fn exports(&self) -> Vec<TypeId> {
vec![]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new().name("TestModule")
}
}
#[test]
fn test_dynamic_module_creation() {
let module = DynamicModule::new("TestModule");
assert_eq!(module.name, "TestModule");
assert!(module.providers.is_empty());
assert!(module.controllers.is_empty());
}
#[test]
fn test_dynamic_module_export_all() {
let module = DynamicModule::new("TestModule").export_all();
assert!(module.export_all);
}
#[test]
fn test_module_builder() {
let module = ModuleBuilder::new("MyModule").export_all().global().build();
assert!(module.export_all);
assert!(module.metadata.global);
}
#[test]
fn test_module_metadata() {
let metadata = ModuleMetadata::new()
.name("MyModule")
.version("1.0.0")
.description("A test module")
.global();
assert_eq!(metadata.name, Some("MyModule".to_string()));
assert_eq!(metadata.version, Some("1.0.0".to_string()));
assert_eq!(metadata.description, Some("A test module".to_string()));
assert!(metadata.global);
}
#[test]
fn test_collect_all_exports() {
#[derive(Clone)]
struct InnerModule;
impl ModuleExt for InnerModule {
fn exports(&self) -> Vec<TypeId> {
vec![TypeId::of::<String>()]
}
}
#[derive(Clone)]
struct OuterModule;
impl ModuleExt for OuterModule {
fn exports(&self) -> Vec<TypeId> {
vec![TypeId::of::<i32>()]
}
fn re_exports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![Box::new(InnerModule)]
}
}
let exports = collect_all_exports(&OuterModule);
assert!(exports.contains(&TypeId::of::<String>()));
assert!(exports.contains(&TypeId::of::<i32>()));
}
#[test]
fn test_module_registry_creation() {
let registry = ModuleRegistry::new();
assert!(registry.guards().is_empty());
}
#[test]
fn test_guard_registration_debug() {
let reg = GuardRegistration {
type_id: TypeId::of::<String>(),
type_name: "TestGuard",
factory: |_| Err(Error::Internal("test".to_string())),
};
let debug_str = format!("{:?}", reg);
assert!(debug_str.contains("TestGuard"));
}
static REGISTRY_SHARED_PROVIDER_INIT_COUNT: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
struct RegistrySharedProvider;
struct RegistrySharedModule;
impl ModuleExt for RegistrySharedModule {
fn providers(&self) -> Vec<ProviderRegistration> {
vec![ProviderRegistration {
type_id: TypeId::of::<RegistrySharedProvider>(),
type_name: "RegistrySharedProvider",
register_fn: |c| {
REGISTRY_SHARED_PROVIDER_INIT_COUNT
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
c.register(RegistrySharedProvider);
},
}]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new().name("RegistrySharedModule")
}
}
struct RegistryLeftModule;
impl ModuleExt for RegistryLeftModule {
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![Box::new(RegistrySharedModule)]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new().name("RegistryLeftModule")
}
}
struct RegistryRightModule;
impl ModuleExt for RegistryRightModule {
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![Box::new(RegistrySharedModule)]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new().name("RegistryRightModule")
}
}
struct RegistryDiamondRootModule;
impl ModuleExt for RegistryDiamondRootModule {
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![Box::new(RegistryLeftModule), Box::new(RegistryRightModule)]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new().name("RegistryDiamondRootModule")
}
}
#[test]
fn test_module_registry_diamond_import_registers_shared_module_once() {
REGISTRY_SHARED_PROVIDER_INIT_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
let container = Container::new();
let mut router = Router::new();
let mut registry = ModuleRegistry::new();
registry
.register_module(&container, &mut router, &RegistryDiamondRootModule)
.unwrap();
assert!(
container.has::<RegistrySharedProvider>(),
"shared module reachable via a diamond must still register"
);
assert_eq!(
REGISTRY_SHARED_PROVIDER_INIT_COUNT.load(std::sync::atomic::Ordering::SeqCst),
1,
"diamond-imported module (reached via two different parents) must \
register exactly once, not zero (dropped) or two (duplicated)"
);
}
struct RegistryCyclicModuleA;
struct RegistryCyclicModuleB;
impl ModuleExt for RegistryCyclicModuleA {
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![Box::new(RegistryCyclicModuleB)]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new().name("RegistryCyclicModuleA")
}
}
impl ModuleExt for RegistryCyclicModuleB {
fn imports(&self) -> Vec<Box<dyn ModuleExt>> {
vec![Box::new(RegistryCyclicModuleA)]
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new().name("RegistryCyclicModuleB")
}
}
#[test]
fn test_module_registry_cyclic_import_does_not_recurse_forever() {
let container = Container::new();
let mut router = Router::new();
let mut registry = ModuleRegistry::new();
registry
.register_module(&container, &mut router, &RegistryCyclicModuleA)
.unwrap();
}
struct DynamicModuleProviderA;
struct DynamicModuleProviderB;
#[test]
fn test_dynamic_module_instances_do_not_collapse_under_dedup() {
let container = Container::new();
let mut router = Router::new();
let mut registry = ModuleRegistry::new();
let module_a = DynamicModule::new("FeatureA").with_provider(ProviderRegistration {
type_id: TypeId::of::<DynamicModuleProviderA>(),
type_name: "DynamicModuleProviderA",
register_fn: |c| c.register(DynamicModuleProviderA),
});
let module_b = DynamicModule::new("FeatureB").with_provider(ProviderRegistration {
type_id: TypeId::of::<DynamicModuleProviderB>(),
type_name: "DynamicModuleProviderB",
register_fn: |c| c.register(DynamicModuleProviderB),
});
registry
.register_module(&container, &mut router, &module_a)
.unwrap();
registry
.register_module(&container, &mut router, &module_b)
.unwrap();
assert!(
container.has::<DynamicModuleProviderA>(),
"first DynamicModule instance's providers must register"
);
assert!(
container.has::<DynamicModuleProviderB>(),
"second, distinctly-configured DynamicModule instance must also \
register -- it must not be dropped as a 'duplicate' of the \
first just because both share the same concrete DynamicModule \
type"
);
}
#[test]
fn test_dynamic_module_instances_do_not_collapse_when_imported() {
let container = Container::new();
let mut router = Router::new();
let mut registry = ModuleRegistry::new();
let feature_a = DynamicModule::new("FeatureA").with_provider(ProviderRegistration {
type_id: TypeId::of::<DynamicModuleProviderA>(),
type_name: "DynamicModuleProviderA",
register_fn: |c| c.register(DynamicModuleProviderA),
});
let feature_b = DynamicModule::new("FeatureB").with_provider(ProviderRegistration {
type_id: TypeId::of::<DynamicModuleProviderB>(),
type_name: "DynamicModuleProviderB",
register_fn: |c| c.register(DynamicModuleProviderB),
});
let root = DynamicModule::new("Root")
.import(feature_a)
.import(feature_b);
registry
.register_module(&container, &mut router, &root)
.unwrap();
assert!(
container.has::<DynamicModuleProviderA>(),
"first imported DynamicModule instance's providers must register"
);
assert!(
container.has::<DynamicModuleProviderB>(),
"second imported DynamicModule instance must also register \
through the ArcModule wrapper, not collapse into the first"
);
}
}