elif_core/modules/
definition.rs

1use crate::container::{Container, ContainerBuilder};
2use crate::errors::CoreError;
3use crate::modules::routing::{RouteDefinition, MiddlewareDefinition};
4
5/// Core module error type
6#[derive(Debug, thiserror::Error)]
7pub enum ModuleError {
8    #[error("Circular dependency detected in module: {module}")]
9    CircularDependency { module: String },
10    
11    #[error("Missing dependency '{dependency}' for module '{module}'")]
12    MissingDependency { module: String, dependency: String },
13    
14    #[error("Module configuration failed: {message}")]
15    ConfigurationFailed { message: String },
16    
17    #[error("Module boot failed: {message}")]
18    BootFailed { message: String },
19    
20    #[error("Container error: {0}")]
21    Container(#[from] CoreError),
22}
23
24/// Application module trait that integrates with the framework
25pub trait Module: Send + Sync {
26    /// Module name for identification
27    fn name(&self) -> &'static str;
28    
29    /// Configure services in the container builder
30    fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError>;
31    
32    /// Define routes for this module
33    fn routes(&self) -> Vec<RouteDefinition> {
34        vec![]
35    }
36    
37    /// Define middleware for this module
38    fn middleware(&self) -> Vec<MiddlewareDefinition> {
39        vec![]
40    }
41    
42    /// Boot the module after container is built
43    fn boot(&self, _container: &Container) -> Result<(), ModuleError> {
44        // Default implementation does nothing
45        Ok(())
46    }
47    
48    /// Module dependencies (other modules that must be loaded first)
49    fn dependencies(&self) -> Vec<&'static str> {
50        vec![]
51    }
52    
53    /// Module version for compatibility checking
54    fn version(&self) -> Option<&'static str> {
55        None
56    }
57    
58    /// Module description
59    fn description(&self) -> Option<&'static str> {
60        None
61    }
62    
63    /// Check if this module can be disabled
64    fn is_optional(&self) -> bool {
65        true
66    }
67}
68
69/// Module metadata for introspection
70#[derive(Debug, Clone)]
71pub struct ModuleMetadata {
72    pub name: String,
73    pub version: Option<String>,
74    pub description: Option<String>,
75    pub dependencies: Vec<String>,
76    pub is_optional: bool,
77    pub route_count: usize,
78    pub middleware_count: usize,
79}
80
81impl ModuleMetadata {
82    /// Create metadata from a module
83    pub fn from_module<M: Module + ?Sized>(module: &M) -> Self {
84        let routes = module.routes();
85        let middleware = module.middleware();
86        
87        Self {
88            name: module.name().to_string(),
89            version: module.version().map(|v| v.to_string()),
90            description: module.description().map(|d| d.to_string()),
91            dependencies: module.dependencies().iter().map(|d| d.to_string()).collect(),
92            is_optional: module.is_optional(),
93            route_count: routes.len(),
94            middleware_count: middleware.len(),
95        }
96    }
97}
98
99/// Base module implementation for common functionality
100#[derive(Debug)]
101pub struct BaseModule {
102    name: &'static str,
103    version: Option<&'static str>,
104    description: Option<&'static str>,
105    dependencies: Vec<&'static str>,
106    is_optional: bool,
107}
108
109impl BaseModule {
110    /// Create a new base module
111    pub fn new(name: &'static str) -> Self {
112        Self {
113            name,
114            version: None,
115            description: None,
116            dependencies: Vec::new(),
117            is_optional: true,
118        }
119    }
120    
121    /// Set module version
122    pub fn with_version(mut self, version: &'static str) -> Self {
123        self.version = Some(version);
124        self
125    }
126    
127    /// Set module description
128    pub fn with_description(mut self, description: &'static str) -> Self {
129        self.description = Some(description);
130        self
131    }
132    
133    /// Set module dependencies
134    pub fn with_dependencies(mut self, dependencies: Vec<&'static str>) -> Self {
135        self.dependencies = dependencies;
136        self
137    }
138    
139    /// Set if module is optional
140    pub fn with_optional(mut self, is_optional: bool) -> Self {
141        self.is_optional = is_optional;
142        self
143    }
144}
145
146impl Module for BaseModule {
147    fn name(&self) -> &'static str {
148        self.name
149    }
150    
151    fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> {
152        // Base module doesn't configure anything by default
153        Ok(builder)
154    }
155    
156    fn dependencies(&self) -> Vec<&'static str> {
157        self.dependencies.clone()
158    }
159    
160    fn version(&self) -> Option<&'static str> {
161        self.version
162    }
163    
164    fn description(&self) -> Option<&'static str> {
165        self.description
166    }
167    
168    fn is_optional(&self) -> bool {
169        self.is_optional
170    }
171}
172
173/// Macro to simplify module creation
174#[macro_export]
175macro_rules! module {
176    (
177        name: $name:expr,
178        $(version: $version:expr,)?
179        $(description: $description:expr,)?
180        $(dependencies: [$($dep:expr),* $(,)?],)?
181        $(optional: $optional:expr,)?
182        configure: |$builder:ident| $config:block
183        $(, boot: |$container:ident| $boot:block)?
184        $(, routes: $routes:expr)?
185        $(, middleware: $middleware:expr)?
186    ) => {
187        {
188            struct CustomModule;
189            
190            impl $crate::modules::Module for CustomModule {
191                fn name(&self) -> &'static str {
192                    $name
193                }
194                
195                $(fn version(&self) -> Option<&'static str> {
196                    Some($version)
197                })?
198                
199                $(fn description(&self) -> Option<&'static str> {
200                    Some($description)
201                })?
202                
203                $(fn dependencies(&self) -> Vec<&'static str> {
204                    vec![$($dep),*]
205                })?
206                
207                $(fn is_optional(&self) -> bool {
208                    $optional
209                })?
210                
211                fn configure(&self, $builder: $crate::container::ContainerBuilder) 
212                    -> Result<$crate::container::ContainerBuilder, $crate::modules::ModuleError> 
213                {
214                    $config
215                }
216                
217                $(fn boot(&self, $container: &$crate::container::Container) 
218                    -> Result<(), $crate::modules::ModuleError> 
219                {
220                    $boot
221                })?
222                
223                $(fn routes(&self) -> Vec<$crate::modules::RouteDefinition> {
224                    $routes
225                })?
226                
227                $(fn middleware(&self) -> Vec<$crate::modules::MiddlewareDefinition> {
228                    $middleware
229                })?
230            }
231            
232            CustomModule
233        }
234    };
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    
241    #[test]
242    fn test_module_metadata() {
243        let base_module = BaseModule::new("test_module")
244            .with_version("1.0.0")
245            .with_description("A test module")
246            .with_dependencies(vec!["dependency1", "dependency2"])
247            .with_optional(false);
248        
249        let metadata = ModuleMetadata::from_module(&base_module);
250        
251        assert_eq!(metadata.name, "test_module");
252        assert_eq!(metadata.version, Some("1.0.0".to_string()));
253        assert_eq!(metadata.description, Some("A test module".to_string()));
254        assert_eq!(metadata.dependencies, vec!["dependency1", "dependency2"]);
255        assert!(!metadata.is_optional);
256    }
257}