elif_core/modules/
definition.rs1use crate::container::{Container, ContainerBuilder};
2use crate::errors::CoreError;
3use crate::modules::routing::{MiddlewareDefinition, RouteDefinition};
4
5#[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
24pub trait Module: Send + Sync {
26 fn name(&self) -> &'static str;
28
29 fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError>;
31
32 fn routes(&self) -> Vec<RouteDefinition> {
34 vec![]
35 }
36
37 fn middleware(&self) -> Vec<MiddlewareDefinition> {
39 vec![]
40 }
41
42 fn boot(&self, _container: &Container) -> Result<(), ModuleError> {
44 Ok(())
46 }
47
48 fn dependencies(&self) -> Vec<&'static str> {
50 vec![]
51 }
52
53 fn version(&self) -> Option<&'static str> {
55 None
56 }
57
58 fn description(&self) -> Option<&'static str> {
60 None
61 }
62
63 fn is_optional(&self) -> bool {
65 true
66 }
67}
68
69#[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 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#[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 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 pub fn with_version(mut self, version: &'static str) -> Self {
127 self.version = Some(version);
128 self
129 }
130
131 pub fn with_description(mut self, description: &'static str) -> Self {
133 self.description = Some(description);
134 self
135 }
136
137 pub fn with_dependencies(mut self, dependencies: Vec<&'static str>) -> Self {
139 self.dependencies = dependencies;
140 self
141 }
142
143 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 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_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}