elif_core/modules/
definition.rs1use crate::container::{Container, ContainerBuilder};
2use crate::errors::CoreError;
3use crate::modules::routing::{RouteDefinition, MiddlewareDefinition};
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.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#[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 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 pub fn with_version(mut self, version: &'static str) -> Self {
123 self.version = Some(version);
124 self
125 }
126
127 pub fn with_description(mut self, description: &'static str) -> Self {
129 self.description = Some(description);
130 self
131 }
132
133 pub fn with_dependencies(mut self, dependencies: Vec<&'static str>) -> Self {
135 self.dependencies = dependencies;
136 self
137 }
138
139 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 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_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}