use crate::container::{Container, ContainerBuilder};
use crate::errors::CoreError;
use crate::modules::routing::{MiddlewareDefinition, RouteDefinition};
#[derive(Debug, thiserror::Error)]
pub enum ModuleError {
#[error("Circular dependency detected in module: {module}")]
CircularDependency { module: String },
#[error("Missing dependency '{dependency}' for module '{module}'")]
MissingDependency { module: String, dependency: String },
#[error("Module configuration failed: {message}")]
ConfigurationFailed { message: String },
#[error("Module boot failed: {message}")]
BootFailed { message: String },
#[error("Container error: {0}")]
Container(#[from] CoreError),
}
pub trait Module: Send + Sync {
fn name(&self) -> &'static str;
fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError>;
fn routes(&self) -> Vec<RouteDefinition> {
vec![]
}
fn middleware(&self) -> Vec<MiddlewareDefinition> {
vec![]
}
fn boot(&self, _container: &Container) -> Result<(), ModuleError> {
Ok(())
}
fn dependencies(&self) -> Vec<&'static str> {
vec![]
}
fn version(&self) -> Option<&'static str> {
None
}
fn description(&self) -> Option<&'static str> {
None
}
fn is_optional(&self) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct ModuleMetadata {
pub name: String,
pub version: Option<String>,
pub description: Option<String>,
pub dependencies: Vec<String>,
pub is_optional: bool,
pub route_count: usize,
pub middleware_count: usize,
}
impl ModuleMetadata {
pub fn from_module<M: Module + ?Sized>(module: &M) -> Self {
let routes = module.routes();
let middleware = module.middleware();
Self {
name: module.name().to_string(),
version: module.version().map(|v| v.to_string()),
description: module.description().map(|d| d.to_string()),
dependencies: module
.dependencies()
.iter()
.map(|d| d.to_string())
.collect(),
is_optional: module.is_optional(),
route_count: routes.len(),
middleware_count: middleware.len(),
}
}
}
#[derive(Debug)]
pub struct BaseModule {
name: &'static str,
version: Option<&'static str>,
description: Option<&'static str>,
dependencies: Vec<&'static str>,
is_optional: bool,
}
impl BaseModule {
pub fn new(name: &'static str) -> Self {
Self {
name,
version: None,
description: None,
dependencies: Vec::new(),
is_optional: true,
}
}
pub fn with_version(mut self, version: &'static str) -> Self {
self.version = Some(version);
self
}
pub fn with_description(mut self, description: &'static str) -> Self {
self.description = Some(description);
self
}
pub fn with_dependencies(mut self, dependencies: Vec<&'static str>) -> Self {
self.dependencies = dependencies;
self
}
pub fn with_optional(mut self, is_optional: bool) -> Self {
self.is_optional = is_optional;
self
}
}
impl Module for BaseModule {
fn name(&self) -> &'static str {
self.name
}
fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> {
Ok(builder)
}
fn dependencies(&self) -> Vec<&'static str> {
self.dependencies.clone()
}
fn version(&self) -> Option<&'static str> {
self.version
}
fn description(&self) -> Option<&'static str> {
self.description
}
fn is_optional(&self) -> bool {
self.is_optional
}
}
#[macro_export]
macro_rules! module {
(
name: $name:expr,
$(version: $version:expr,)?
$(description: $description:expr,)?
$(dependencies: [$($dep:expr),* $(,)?],)?
$(optional: $optional:expr,)?
configure: |$builder:ident| $config:block
$(, boot: |$container:ident| $boot:block)?
$(, routes: $routes:expr)?
$(, middleware: $middleware:expr)?
) => {
{
struct CustomModule;
impl $crate::modules::Module for CustomModule {
fn name(&self) -> &'static str {
$name
}
$(fn version(&self) -> Option<&'static str> {
Some($version)
})?
$(fn description(&self) -> Option<&'static str> {
Some($description)
})?
$(fn dependencies(&self) -> Vec<&'static str> {
vec![$($dep),*]
})?
$(fn is_optional(&self) -> bool {
$optional
})?
fn configure(&self, $builder: $crate::container::ContainerBuilder)
-> Result<$crate::container::ContainerBuilder, $crate::modules::ModuleError>
{
$config
}
$(fn boot(&self, $container: &$crate::container::Container)
-> Result<(), $crate::modules::ModuleError>
{
$boot
})?
$(fn routes(&self) -> Vec<$crate::modules::RouteDefinition> {
$routes
})?
$(fn middleware(&self) -> Vec<$crate::modules::MiddlewareDefinition> {
$middleware
})?
}
CustomModule
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_metadata() {
let base_module = BaseModule::new("test_module")
.with_version("1.0.0")
.with_description("A test module")
.with_dependencies(vec!["dependency1", "dependency2"])
.with_optional(false);
let metadata = ModuleMetadata::from_module(&base_module);
assert_eq!(metadata.name, "test_module");
assert_eq!(metadata.version, Some("1.0.0".to_string()));
assert_eq!(metadata.description, Some("A test module".to_string()));
assert_eq!(metadata.dependencies, vec!["dependency1", "dependency2"]);
assert!(!metadata.is_optional);
}
}