elif_core/modules/
definition.rs

1use crate::container::{Container, ContainerBuilder};
2use crate::errors::CoreError;
3use crate::modules::routing::{MiddlewareDefinition, RouteDefinition};
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
92                .dependencies()
93                .iter()
94                .map(|d| d.to_string())
95                .collect(),
96            is_optional: module.is_optional(),
97            route_count: routes.len(),
98            middleware_count: middleware.len(),
99        }
100    }
101}
102
103/// Base module implementation for common functionality
104#[derive(Debug)]
105pub struct BaseModule {
106    name: &'static str,
107    version: Option<&'static str>,
108    description: Option<&'static str>,
109    dependencies: Vec<&'static str>,
110    is_optional: bool,
111}
112
113impl BaseModule {
114    /// Create a new base module
115    pub fn new(name: &'static str) -> Self {
116        Self {
117            name,
118            version: None,
119            description: None,
120            dependencies: Vec::new(),
121            is_optional: true,
122        }
123    }
124
125    /// Set module version
126    pub fn with_version(mut self, version: &'static str) -> Self {
127        self.version = Some(version);
128        self
129    }
130
131    /// Set module description
132    pub fn with_description(mut self, description: &'static str) -> Self {
133        self.description = Some(description);
134        self
135    }
136
137    /// Set module dependencies
138    pub fn with_dependencies(mut self, dependencies: Vec<&'static str>) -> Self {
139        self.dependencies = dependencies;
140        self
141    }
142
143    /// Set if module is optional
144    pub fn with_optional(mut self, is_optional: bool) -> Self {
145        self.is_optional = is_optional;
146        self
147    }
148}
149
150impl Module for BaseModule {
151    fn name(&self) -> &'static str {
152        self.name
153    }
154
155    fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> {
156        // Base module doesn't configure anything by default
157        Ok(builder)
158    }
159
160    fn dependencies(&self) -> Vec<&'static str> {
161        self.dependencies.clone()
162    }
163
164    fn version(&self) -> Option<&'static str> {
165        self.version
166    }
167
168    fn description(&self) -> Option<&'static str> {
169        self.description
170    }
171
172    fn is_optional(&self) -> bool {
173        self.is_optional
174    }
175}
176
177/// Macro to simplify module creation
178#[macro_export]
179macro_rules! module {
180    (
181        name: $name:expr,
182        $(version: $version:expr,)?
183        $(description: $description:expr,)?
184        $(dependencies: [$($dep:expr),* $(,)?],)?
185        $(optional: $optional:expr,)?
186        configure: |$builder:ident| $config:block
187        $(, boot: |$container:ident| $boot:block)?
188        $(, routes: $routes:expr)?
189        $(, middleware: $middleware:expr)?
190    ) => {
191        {
192            struct CustomModule;
193
194            impl $crate::modules::Module for CustomModule {
195                fn name(&self) -> &'static str {
196                    $name
197                }
198
199                $(fn version(&self) -> Option<&'static str> {
200                    Some($version)
201                })?
202
203                $(fn description(&self) -> Option<&'static str> {
204                    Some($description)
205                })?
206
207                $(fn dependencies(&self) -> Vec<&'static str> {
208                    vec![$($dep),*]
209                })?
210
211                $(fn is_optional(&self) -> bool {
212                    $optional
213                })?
214
215                fn configure(&self, $builder: $crate::container::ContainerBuilder)
216                    -> Result<$crate::container::ContainerBuilder, $crate::modules::ModuleError>
217                {
218                    $config
219                }
220
221                $(fn boot(&self, $container: &$crate::container::Container)
222                    -> Result<(), $crate::modules::ModuleError>
223                {
224                    $boot
225                })?
226
227                $(fn routes(&self) -> Vec<$crate::modules::RouteDefinition> {
228                    $routes
229                })?
230
231                $(fn middleware(&self) -> Vec<$crate::modules::MiddlewareDefinition> {
232                    $middleware
233                })?
234            }
235
236            CustomModule
237        }
238    };
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_module_metadata() {
247        let base_module = BaseModule::new("test_module")
248            .with_version("1.0.0")
249            .with_description("A test module")
250            .with_dependencies(vec!["dependency1", "dependency2"])
251            .with_optional(false);
252
253        let metadata = ModuleMetadata::from_module(&base_module);
254
255        assert_eq!(metadata.name, "test_module");
256        assert_eq!(metadata.version, Some("1.0.0".to_string()));
257        assert_eq!(metadata.description, Some("A test module".to_string()));
258        assert_eq!(metadata.dependencies, vec!["dependency1", "dependency2"]);
259        assert!(!metadata.is_optional);
260    }
261}