1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
//! Bootstrap engine that orchestrates module discovery, dependency resolution,
//! container configuration, and server startup.
use crate::{
bootstrap::{BootstrapError, BootstrapResult, ControllerRegistry},
config::HttpConfig,
routing::ElifRouter,
server::Server,
Middleware,
};
use elif_core::{
container::IocContainer,
modules::{get_global_module_registry, CompileTimeModuleMetadata, ModuleDescriptor, ModuleRuntime, ModuleRuntimeError},
};
use std::{future::Future, net::SocketAddr, pin::Pin, sync::Arc};
/// The main bootstrap orchestrator that handles the complete app startup process
///
/// This struct provides a fluent API for configuring and starting an elif.rs application:
/// - Discovers all modules automatically via the compile-time registry
/// - Resolves module dependencies using ModuleRuntime with enhanced error handling
/// - Configures the DI container with all providers
/// - Registers all controllers and their routes
/// - Sets up middleware pipeline
/// - Starts the HTTP server
#[derive(Debug)]
pub struct AppBootstrapper {
/// Discovered module metadata from compile-time registry
modules: Vec<CompileTimeModuleMetadata>,
/// Runtime module system for enhanced dependency resolution and lifecycle management
module_runtime: ModuleRuntime,
/// HTTP server configuration
config: HttpConfig,
/// Middleware stack to apply
middleware: Vec<Box<dyn Middleware>>,
/// Custom DI container if provided
container: Option<IocContainer>,
}
impl AppBootstrapper {
/// Create a new AppBootstrapper with automatic module discovery
///
/// This method:
/// 1. Gets all modules from the global compile-time registry
/// 2. Converts them to ModuleDescriptors for ModuleRuntime
/// 3. Uses ModuleRuntime for enhanced dependency resolution and lifecycle management
/// 4. Sets up default configuration
pub fn new() -> BootstrapResult<Self> {
let registry = get_global_module_registry();
// Get all modules from compile-time registry
let modules: Vec<CompileTimeModuleMetadata> = registry.all_modules()
.into_iter()
.cloned()
.collect();
if modules.is_empty() {
return Err(BootstrapError::ModuleDiscoveryFailed {
message: "No modules found. Make sure you have modules decorated with #[module]".to_string(),
});
}
// Create ModuleRuntime and register all modules
let mut module_runtime = ModuleRuntime::new();
for module_metadata in &modules {
let descriptor = Self::convert_metadata_to_descriptor(module_metadata);
module_runtime.register_module(descriptor)
.map_err(|e| BootstrapError::ModuleRegistrationFailed {
message: format!("Failed to register module '{}': {}", module_metadata.name, e),
})?;
}
// Calculate load order using ModuleRuntime's sophisticated dependency resolution
let load_order = module_runtime.calculate_load_order()
.map_err(|e| match e {
ModuleRuntimeError::CircularDependency { cycle, message: _ } => {
BootstrapError::CircularDependency { cycle }
}
ModuleRuntimeError::MissingDependency { module, missing_dependency, message: _ } => {
BootstrapError::MissingDependency {
module,
dependency: missing_dependency,
}
}
other => BootstrapError::ModuleRegistrationFailed {
message: format!("Module dependency resolution failed: {}", other),
}
})?;
tracing::info!("Bootstrap: Discovered {} modules", modules.len());
tracing::info!("Bootstrap: Load order resolved: {:?}", load_order);
Ok(AppBootstrapper {
modules,
module_runtime,
config: HttpConfig::default(),
middleware: Vec::new(),
container: None,
})
}
/// Convert CompileTimeModuleMetadata to ModuleDescriptor for ModuleRuntime
fn convert_metadata_to_descriptor(metadata: &CompileTimeModuleMetadata) -> ModuleDescriptor {
ModuleDescriptor {
name: metadata.name.clone(),
version: None,
description: None,
providers: Vec::new(), // CompileTimeModuleMetadata only has provider names, not full descriptors
controllers: Vec::new(), // CompileTimeModuleMetadata only has controller names, not full descriptors
imports: metadata.imports.clone(),
exports: metadata.exports.clone(),
dependencies: metadata.imports.clone(), // In this context, imports are dependencies
is_optional: false,
}
}
/// Configure the HTTP server with custom configuration
pub fn with_config(mut self, config: HttpConfig) -> Self {
self.config = config;
self
}
/// Add middleware to the application
pub fn with_middleware(mut self, middleware: Vec<Box<dyn Middleware>>) -> Self {
self.middleware = middleware;
self
}
/// Use a pre-configured DI container
pub fn with_container(mut self, container: IocContainer) -> Self {
self.container = Some(container);
self
}
/// Start the HTTP server on the specified address
///
/// This method performs the complete bootstrap sequence:
/// 1. Configures the DI container with all module providers using ModuleRuntime
/// 2. Creates and configures the router with all controller routes
/// 3. Sets up the middleware pipeline
/// 4. Starts the HTTP server
pub fn listen(
mut self,
addr: impl Into<SocketAddr> + Send + 'static,
) -> Pin<Box<dyn Future<Output = BootstrapResult<()>> + Send>> {
Box::pin(async move {
let addr = addr.into();
tracing::info!("Bootstrap: Starting server on {}", addr);
// Step 1: Configure DI container using ModuleRuntime
let container = self.configure_container().await?;
// Step 2: Create and configure router with container access
let router = self.configure_router(&container).await?;
// Step 3: Create and start server
// Note: Creating a new container for server since IocContainer doesn't implement Clone
// This will be enhanced once the IoC container supports better sharing patterns
let server_container = IocContainer::new();
let mut server = Server::new(server_container, self.config)
.map_err(|e| BootstrapError::ServerStartupFailed {
message: format!("Failed to create server: {}", e),
})?;
// Apply middleware and use router
if !self.middleware.is_empty() {
// TODO: Apply middleware to server
// For now, just use the server as-is
}
server.use_router(router);
// Start listening - convert SocketAddr to string
server
.listen(addr.to_string())
.await
.map_err(|e| BootstrapError::ServerStartupFailed {
message: format!("Failed to start server: {}", e),
})?;
Ok(())
})
}
/// Configure the DI container with all module providers using ModuleRuntime
async fn configure_container(&mut self) -> BootstrapResult<Arc<IocContainer>> {
let mut container = if let Some(_provided_container) = &self.container {
// TODO: Proper container merging/extension when IocContainer supports it
// For now, we create a new container and document this limitation
tracing::warn!("Bootstrap: Custom container provided but cannot be cloned. Creating new container with module configurations.");
tracing::info!("Bootstrap: To properly use custom containers, consider configuring modules directly on your container before bootstrap");
IocContainer::new()
} else {
IocContainer::new()
};
// Use ModuleRuntime's sophisticated dependency resolution and container configuration
self.module_runtime.resolve_dependencies(&mut container)
.await
.map_err(|e| BootstrapError::ContainerConfigurationFailed {
message: format!("ModuleRuntime dependency resolution failed: {}", e),
})?;
tracing::info!("Bootstrap: Container configured with {} modules using ModuleRuntime", self.modules.len());
Ok(Arc::new(container))
}
/// Configure the router with all controller routes
async fn configure_router(&self, container: &Arc<IocContainer>) -> BootstrapResult<ElifRouter> {
let mut router = ElifRouter::new();
// Create ControllerRegistry with IoC container access
let controller_registry = ControllerRegistry::from_modules(&self.modules, container.clone())
.map_err(|e| BootstrapError::ControllerRegistrationFailed {
message: format!("Failed to create controller registry: {}", e),
})?;
tracing::info!("Bootstrap: Created controller registry with {} controllers",
controller_registry.get_controller_names().len());
// Validate all routes for conflicts before registration
if let Err(conflicts) = controller_registry.validate_routes() {
let mut error_message = String::new();
for conflict in &conflicts {
error_message.push_str(&format!("\n - {}/{}: {} vs {}/{}",
conflict.route1.method, conflict.route1.path,
conflict.route1.controller,
conflict.route2.method, conflict.route2.path));
}
return Err(BootstrapError::RouteRegistrationFailed {
message: format!("Route conflicts detected:{}", error_message),
});
}
// Register all controllers automatically
router = controller_registry.register_all_routes(router)
.map_err(|e| BootstrapError::ControllerRegistrationFailed {
message: format!("Failed to register controller routes: {}", e),
})?;
tracing::info!("Bootstrap: Successfully registered {} controller routes",
controller_registry.total_routes());
tracing::info!("Bootstrap: Router configured with controllers from {} modules", self.modules.len());
Ok(router)
}
/// Get discovered modules (for debugging/introspection)
pub fn modules(&self) -> &[CompileTimeModuleMetadata] {
&self.modules
}
/// Get module load order from ModuleRuntime (for debugging/introspection)
pub fn load_order(&self) -> &[String] {
self.module_runtime.load_order()
}
}
impl Default for AppBootstrapper {
fn default() -> Self {
Self::new().unwrap_or_else(|e| {
tracing::error!("Failed to create default AppBootstrapper: {}", e);
panic!("Failed to create default AppBootstrapper: {}", e);
})
}
}
/// Utility function to create a bootstrapper directly (for convenience)
pub fn create_bootstrapper() -> BootstrapResult<AppBootstrapper> {
AppBootstrapper::new()
}
#[cfg(test)]
mod tests {
use super::*;
use elif_core::modules::{register_module_globally, CompileTimeModuleMetadata};
#[tokio::test]
async fn test_bootstrap_creation() {
// Register a test module
let test_module = CompileTimeModuleMetadata::new("TestModule".to_string())
.with_controller("TestController".to_string())
.with_provider("TestService".to_string());
register_module_globally(test_module);
let bootstrapper = AppBootstrapper::new().expect("Should create bootstrapper");
assert!(!bootstrapper.modules().is_empty());
assert!(bootstrapper.modules().iter().any(|m| m.name == "TestModule"));
}
#[tokio::test]
async fn test_bootstrap_configuration() {
let test_module = CompileTimeModuleMetadata::new("ConfigTestModule".to_string());
register_module_globally(test_module);
let config = HttpConfig::default();
let bootstrapper = AppBootstrapper::new()
.expect("Should create bootstrapper")
.with_config(config);
// Just test that configuration doesn't panic
assert!(!bootstrapper.modules().is_empty());
}
#[tokio::test]
async fn test_bootstrap_error_handling() {
// Test what happens when no modules are registered
// Note: This test may be affected by other tests registering modules
// In a real scenario, you'd want to use a separate test registry
// The current implementation will find modules from other tests,
// but in principle, if no modules were found, it should return an error
let result = AppBootstrapper::new();
// Either succeeds (because other tests registered modules)
// or fails with a clear error message
match result {
Ok(bootstrapper) => {
// Other tests registered modules, that's fine
assert!(!bootstrapper.modules().is_empty());
}
Err(BootstrapError::ModuleDiscoveryFailed { message }) => {
// This is the expected error when no modules are found
assert!(message.contains("No modules found"));
}
Err(other) => {
panic!("Unexpected error type: {:?}", other);
}
}
}
}