1use crate::container::{Container, ContainerBuilder};
2use crate::provider::{ProviderRegistry, ServiceProvider};
3use std::collections::HashMap;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7use thiserror::Error;
8use tokio::signal;
9use tokio::sync::mpsc;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub enum HttpMethod {
14 GET,
15 POST,
16 PUT,
17 PATCH,
18 DELETE,
19 OPTIONS,
20 HEAD,
21}
22
23#[derive(Debug, Clone)]
25pub struct RouteDefinition {
26 pub method: HttpMethod,
27 pub path: String,
28 pub handler: String, pub middleware: Vec<String>, pub description: Option<String>,
31}
32
33impl RouteDefinition {
34 pub fn new(method: HttpMethod, path: impl Into<String>, handler: impl Into<String>) -> Self {
35 Self {
36 method,
37 path: path.into(),
38 handler: handler.into(),
39 middleware: Vec::new(),
40 description: None,
41 }
42 }
43
44 pub fn with_middleware(mut self, middleware: Vec<String>) -> Self {
45 self.middleware = middleware;
46 self
47 }
48
49 pub fn with_description(mut self, description: impl Into<String>) -> Self {
50 self.description = Some(description.into());
51 self
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct MiddlewareDefinition {
58 pub name: String,
59 pub priority: i32, pub description: Option<String>,
61}
62
63impl MiddlewareDefinition {
64 pub fn new(name: impl Into<String>, priority: i32) -> Self {
65 Self {
66 name: name.into(),
67 priority,
68 description: None,
69 }
70 }
71
72 pub fn with_description(mut self, description: impl Into<String>) -> Self {
73 self.description = Some(description.into());
74 self
75 }
76}
77
78pub trait Module: Send + Sync {
80 fn name(&self) -> &'static str;
82
83 fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError>;
85
86 fn routes(&self) -> Vec<RouteDefinition> {
88 vec![]
89 }
90
91 fn middleware(&self) -> Vec<MiddlewareDefinition> {
93 vec![]
94 }
95
96 fn boot(&self, _container: &Container) -> Result<(), ModuleError> {
98 Ok(())
100 }
101
102 fn dependencies(&self) -> Vec<&'static str> {
104 vec![]
105 }
106}
107
108pub struct ModuleRegistry {
110 modules: Vec<Box<dyn Module>>,
111 loading_order: Vec<usize>,
112 routes: Vec<RouteDefinition>,
113 middleware: Vec<MiddlewareDefinition>,
114}
115
116impl ModuleRegistry {
117 pub fn new() -> Self {
118 Self {
119 modules: Vec::new(),
120 loading_order: Vec::new(),
121 routes: Vec::new(),
122 middleware: Vec::new(),
123 }
124 }
125
126 pub fn register<M: Module + 'static>(&mut self, module: M) {
128 self.modules.push(Box::new(module));
129 }
130
131 pub fn resolve_dependencies(&mut self) -> Result<(), ModuleError> {
133 let module_count = self.modules.len();
134
135 let name_to_index: HashMap<String, usize> = self.modules
137 .iter()
138 .enumerate()
139 .map(|(i, m)| (m.name().to_string(), i))
140 .collect();
141
142 let mut visited = vec![false; module_count];
144 let mut temp_mark = vec![false; module_count];
145 let mut result = Vec::new();
146
147 for i in 0..module_count {
148 if !visited[i] {
149 self.visit_module(i, &name_to_index, &mut visited, &mut temp_mark, &mut result)?;
150 }
151 }
152
153 self.loading_order = result;
154 Ok(())
155 }
156
157 fn visit_module(
159 &self,
160 index: usize,
161 name_to_index: &HashMap<String, usize>,
162 visited: &mut Vec<bool>,
163 temp_mark: &mut Vec<bool>,
164 result: &mut Vec<usize>,
165 ) -> Result<(), ModuleError> {
166 if temp_mark[index] {
167 return Err(ModuleError::CircularDependency {
168 module: self.modules[index].name().to_string(),
169 });
170 }
171
172 if visited[index] {
173 return Ok(());
174 }
175
176 temp_mark[index] = true;
177
178 let dependencies = self.modules[index].dependencies();
180 for dep_name in dependencies {
181 if let Some(&dep_index) = name_to_index.get(dep_name) {
182 self.visit_module(dep_index, name_to_index, visited, temp_mark, result)?;
183 } else {
184 return Err(ModuleError::MissingDependency {
185 module: self.modules[index].name().to_string(),
186 dependency: dep_name.to_string(),
187 });
188 }
189 }
190
191 temp_mark[index] = false;
192 visited[index] = true;
193 result.push(index);
194
195 Ok(())
196 }
197
198 pub fn configure_all(&self, mut builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> {
200 for &index in &self.loading_order {
201 let module = &self.modules[index];
202 builder = module.configure(builder)
203 .map_err(|e| ModuleError::ConfigurationFailed {
204 module: module.name().to_string(),
205 error: e.to_string(),
206 })?;
207 }
208 Ok(builder)
209 }
210
211 pub fn boot_all(&self, container: &Container) -> Result<(), ModuleError> {
213 for &index in &self.loading_order {
214 let module = &self.modules[index];
215 module.boot(container)
216 .map_err(|e| ModuleError::BootFailed {
217 module: module.name().to_string(),
218 error: e.to_string(),
219 })?;
220 }
221 Ok(())
222 }
223
224 pub fn collect_routes(&mut self) -> &[RouteDefinition] {
226 self.routes.clear();
227 for module in &self.modules {
228 self.routes.extend(module.routes());
229 }
230 &self.routes
231 }
232
233 pub fn collect_middleware(&mut self) -> &[MiddlewareDefinition] {
235 self.middleware.clear();
236 for module in &self.modules {
237 self.middleware.extend(module.middleware());
238 }
239 self.middleware.sort_by_key(|m| m.priority);
241 &self.middleware
242 }
243
244 pub fn module_names(&self) -> Vec<&str> {
246 self.modules.iter().map(|m| m.name()).collect()
247 }
248
249 pub fn loading_order(&self) -> &[usize] {
251 &self.loading_order
252 }
253}
254
255#[derive(Debug, Clone, PartialEq)]
257pub enum ApplicationState {
258 Created,
259 Starting,
260 Running,
261 Stopping,
262 Stopped,
263 Failed(String),
264}
265
266pub trait LifecycleHook: Send + Sync {
269 fn name(&self) -> &'static str;
271
272 fn before_start<'life0, 'async_trait>(
274 &'life0 self,
275 container: &'life0 Container,
276 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
277 where
278 'life0: 'async_trait,
279 Self: 'async_trait,
280 {
281 Box::pin(async move { Ok(()) })
282 }
283
284 fn after_start<'life0, 'async_trait>(
286 &'life0 self,
287 container: &'life0 Container,
288 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
289 where
290 'life0: 'async_trait,
291 Self: 'async_trait,
292 {
293 Box::pin(async move { Ok(()) })
294 }
295
296 fn before_stop<'life0, 'async_trait>(
298 &'life0 self,
299 container: &'life0 Container,
300 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
301 where
302 'life0: 'async_trait,
303 Self: 'async_trait,
304 {
305 Box::pin(async move { Ok(()) })
306 }
307
308 fn after_stop<'life0, 'async_trait>(
310 &'life0 self,
311 container: &'life0 Container,
312 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
313 where
314 'life0: 'async_trait,
315 Self: 'async_trait,
316 {
317 Box::pin(async move { Ok(()) })
318 }
319}
320
321pub struct Application {
323 container: Container,
324 modules: ModuleRegistry,
325 providers: ProviderRegistry,
326 state: ApplicationState,
327 shutdown_signal: Arc<AtomicBool>,
328 lifecycle_hooks: Vec<Box<dyn LifecycleHook>>,
329 startup_time: Option<Instant>,
330 shutdown_timeout: Duration,
331}
332
333impl Application {
334 pub fn builder() -> ApplicationBuilder {
336 ApplicationBuilder::new()
337 }
338
339 pub fn container(&self) -> &Container {
341 &self.container
342 }
343
344 pub fn state(&self) -> &ApplicationState {
346 &self.state
347 }
348
349 pub fn modules(&mut self) -> &mut ModuleRegistry {
351 &mut self.modules
352 }
353
354 pub fn routes(&mut self) -> &[RouteDefinition] {
356 self.modules.collect_routes()
357 }
358
359 pub fn middleware(&mut self) -> &[MiddlewareDefinition] {
361 self.modules.collect_middleware()
362 }
363
364 pub fn uptime(&self) -> Option<Duration> {
366 self.startup_time.map(|start| start.elapsed())
367 }
368
369 pub fn is_running(&self) -> bool {
371 self.state == ApplicationState::Running
372 }
373
374 pub async fn start(&mut self) -> Result<(), ApplicationError> {
376 if self.state != ApplicationState::Created {
377 return Err(ApplicationError::InvalidState {
378 current: format!("{:?}", self.state),
379 expected: "Created".to_string(),
380 });
381 }
382
383 self.state = ApplicationState::Starting;
384 let start_time = Instant::now();
385
386 for hook in &self.lifecycle_hooks {
388 if let Err(e) = hook.before_start(&self.container).await {
389 self.state = ApplicationState::Failed(e.to_string());
390 return Err(ApplicationError::LifecycleHookFailed {
391 hook: hook.name().to_string(),
392 phase: "before_start".to_string(),
393 error: e.to_string(),
394 });
395 }
396 }
397
398 if let Err(e) = self.providers.boot_all(&self.container) {
400 self.state = ApplicationState::Failed(e.to_string());
401 return Err(ApplicationError::ProviderBoot(e));
402 }
403
404 if let Err(e) = self.modules.boot_all(&self.container) {
406 self.state = ApplicationState::Failed(e.to_string());
407 return Err(ApplicationError::ModuleBoot(e));
408 }
409
410 self.state = ApplicationState::Running;
411 self.startup_time = Some(start_time);
412
413 for hook in &self.lifecycle_hooks {
415 if let Err(e) = hook.after_start(&self.container).await {
416 tracing::warn!("After start hook '{}' failed: {}", hook.name(), e);
417 }
419 }
420
421 let startup_duration = start_time.elapsed();
422 tracing::info!("Application started successfully in {:?}", startup_duration);
423
424 Ok(())
425 }
426
427 pub async fn run(&mut self) -> Result<(), ApplicationError> {
429 self.start().await?;
430
431 let shutdown_signal = self.shutdown_signal.clone();
433 let (tx, mut rx) = mpsc::channel::<()>(1);
434
435 tokio::spawn(async move {
437 let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate())
438 .expect("Failed to install SIGTERM handler");
439 let mut sigint = signal::unix::signal(signal::unix::SignalKind::interrupt())
440 .expect("Failed to install SIGINT handler");
441
442 tokio::select! {
443 _ = sigterm.recv() => {
444 tracing::info!("Received SIGTERM, initiating graceful shutdown");
445 }
446 _ = sigint.recv() => {
447 tracing::info!("Received SIGINT, initiating graceful shutdown");
448 }
449 }
450
451 shutdown_signal.store(true, Ordering::SeqCst);
452 let _ = tx.send(()).await;
453 });
454
455 rx.recv().await;
457
458 self.shutdown().await?;
460
461 Ok(())
462 }
463
464 pub async fn shutdown(&mut self) -> Result<(), ApplicationError> {
466 if self.state != ApplicationState::Running {
467 return Err(ApplicationError::InvalidState {
468 current: format!("{:?}", self.state),
469 expected: "Running".to_string(),
470 });
471 }
472
473 self.state = ApplicationState::Stopping;
474 let shutdown_start = Instant::now();
475
476 tracing::info!("Beginning graceful shutdown...");
477
478 for hook in &self.lifecycle_hooks {
480 if let Err(e) = hook.before_stop(&self.container).await {
481 tracing::warn!("Before stop hook '{}' failed: {}", hook.name(), e);
482 }
484 }
485
486 let shutdown_result = tokio::time::timeout(
488 self.shutdown_timeout,
489 self.perform_shutdown()
490 ).await;
491
492 match shutdown_result {
493 Ok(Ok(())) => {
494 for hook in &self.lifecycle_hooks {
496 if let Err(e) = hook.after_stop(&self.container).await {
497 tracing::warn!("After stop hook '{}' failed: {}", hook.name(), e);
498 }
499 }
500
501 self.state = ApplicationState::Stopped;
502 let shutdown_duration = shutdown_start.elapsed();
503 tracing::info!("Application stopped gracefully in {:?}", shutdown_duration);
504 Ok(())
505 }
506 Ok(Err(e)) => {
507 self.state = ApplicationState::Failed(e.to_string());
508 Err(e)
509 }
510 Err(_) => {
511 self.state = ApplicationState::Failed("Shutdown timeout".to_string());
512 Err(ApplicationError::ShutdownTimeout {
513 timeout: self.shutdown_timeout,
514 })
515 }
516 }
517 }
518
519 async fn perform_shutdown(&self) -> Result<(), ApplicationError> {
521 tokio::time::sleep(Duration::from_millis(100)).await;
529
530 Ok(())
531 }
532
533 pub fn request_shutdown(&self) {
535 self.shutdown_signal.store(true, Ordering::SeqCst);
536 }
537
538 pub fn shutdown_requested(&self) -> bool {
540 self.shutdown_signal.load(Ordering::SeqCst)
541 }
542}
543
544pub struct ApplicationBuilder {
546 modules: ModuleRegistry,
547 providers: ProviderRegistry,
548 lifecycle_hooks: Vec<Box<dyn LifecycleHook>>,
549 shutdown_timeout: Duration,
550}
551
552impl ApplicationBuilder {
553 pub fn new() -> Self {
554 Self {
555 modules: ModuleRegistry::new(),
556 providers: ProviderRegistry::new(),
557 lifecycle_hooks: Vec::new(),
558 shutdown_timeout: Duration::from_secs(30), }
560 }
561
562 pub fn module<M: Module + 'static>(mut self, module: M) -> Self {
564 self.modules.register(module);
565 self
566 }
567
568 pub fn provider<P: ServiceProvider + 'static>(mut self, provider: P) -> Self {
570 self.providers.register(provider);
571 self
572 }
573
574 pub fn lifecycle_hook<H: LifecycleHook + 'static>(mut self, hook: H) -> Self {
576 self.lifecycle_hooks.push(Box::new(hook));
577 self
578 }
579
580 pub fn shutdown_timeout(mut self, timeout: Duration) -> Self {
582 self.shutdown_timeout = timeout;
583 self
584 }
585
586 pub fn build(mut self) -> Result<Application, ApplicationError> {
588 self.providers.resolve_dependencies()
590 .map_err(ApplicationError::ProviderDependency)?;
591
592 self.modules.resolve_dependencies()
594 .map_err(ApplicationError::ModuleDependency)?;
595
596 let mut builder = Container::builder();
598
599 builder = self.providers.register_all(builder)
601 .map_err(ApplicationError::ProviderRegistration)?;
602
603 builder = self.modules.configure_all(builder)
605 .map_err(ApplicationError::ModuleConfiguration)?;
606
607 let container = builder.build()
608 .map_err(|e| ApplicationError::ContainerBuild {
609 error: e.to_string(),
610 })?;
611
612 Ok(Application {
613 container,
614 modules: self.modules,
615 providers: self.providers,
616 state: ApplicationState::Created,
617 shutdown_signal: Arc::new(AtomicBool::new(false)),
618 lifecycle_hooks: self.lifecycle_hooks,
619 startup_time: None,
620 shutdown_timeout: self.shutdown_timeout,
621 })
622 }
623
624}
625
626#[derive(Error, Debug)]
627pub enum ModuleError {
628 #[error("Module configuration failed for '{module}': {error}")]
629 ConfigurationFailed { module: String, error: String },
630
631 #[error("Module boot failed for '{module}': {error}")]
632 BootFailed { module: String, error: String },
633
634 #[error("Circular dependency detected for module '{module}'")]
635 CircularDependency { module: String },
636
637 #[error("Missing dependency '{dependency}' for module '{module}'")]
638 MissingDependency { module: String, dependency: String },
639
640 #[error("Service registration failed: {service}")]
641 ServiceRegistrationFailed { service: String },
642}
643
644#[derive(Error, Debug)]
645pub enum ApplicationError {
646 #[error("Module configuration error: {0}")]
647 ModuleConfiguration(#[from] ModuleError),
648
649 #[error("Module boot error: {0}")]
650 ModuleBoot(ModuleError),
651
652 #[error("Module dependency error: {0}")]
653 ModuleDependency(ModuleError),
654
655 #[error("Container build failed: {error}")]
656 ContainerBuild { error: String },
657
658 #[error("Circular dependency detected for module '{module}'")]
659 CircularDependency { module: String },
660
661 #[error("Missing dependency '{dependency}' for module '{module}'")]
662 MissingDependency { module: String, dependency: String },
663
664 #[error("Provider dependency error: {0}")]
665 ProviderDependency(#[from] crate::provider::ProviderError),
666
667 #[error("Provider registration error: {0}")]
668 ProviderRegistration(crate::provider::ProviderError),
669
670 #[error("Provider boot error: {0}")]
671 ProviderBoot(crate::provider::ProviderError),
672
673 #[error("Invalid application state: expected {expected}, found {current}")]
674 InvalidState { current: String, expected: String },
675
676 #[error("Lifecycle hook '{hook}' failed during {phase}: {error}")]
677 LifecycleHookFailed { hook: String, phase: String, error: String },
678
679 #[error("Application shutdown timeout after {timeout:?}")]
680 ShutdownTimeout { timeout: Duration },
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686 use crate::app_config::{AppConfig, Environment};
687 use crate::container::DatabaseConnection;
688 use std::sync::Arc;
689
690 fn create_test_config() -> AppConfig {
692 AppConfig {
693 name: "test-app".to_string(),
694 environment: Environment::Testing,
695 database_url: "sqlite::memory:".to_string(),
696 jwt_secret: Some("test-secret".to_string()),
697 server: crate::app_config::ServerConfig {
698 host: "127.0.0.1".to_string(),
699 port: 8080,
700 workers: 4,
701 },
702 logging: crate::app_config::LoggingConfig {
703 level: "info".to_string(),
704 format: "compact".to_string(),
705 },
706 }
707 }
708
709 struct TestDatabase;
710 impl DatabaseConnection for TestDatabase {
711 fn is_connected(&self) -> bool { true }
712 fn execute(&self, _query: &str) -> Result<(), crate::container::DatabaseError> { Ok(()) }
713 }
714
715 struct CoreModule;
717 impl Module for CoreModule {
718 fn name(&self) -> &'static str { "core" }
719
720 fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> {
721 Ok(builder)
723 }
724
725 fn routes(&self) -> Vec<RouteDefinition> {
726 vec![
727 RouteDefinition::new(HttpMethod::GET, "/", "CoreController::index")
728 .with_description("Core module home route"),
729 RouteDefinition::new(HttpMethod::GET, "/health", "CoreController::health")
730 .with_middleware(vec!["logging".to_string()])
731 .with_description("Health check endpoint"),
732 ]
733 }
734
735 fn middleware(&self) -> Vec<MiddlewareDefinition> {
736 vec![
737 MiddlewareDefinition::new("logging", 100)
738 .with_description("Request logging middleware"),
739 MiddlewareDefinition::new("cors", 200)
740 .with_description("CORS handling middleware"),
741 ]
742 }
743 }
744
745 struct AuthModule;
746 impl Module for AuthModule {
747 fn name(&self) -> &'static str { "auth" }
748
749 fn dependencies(&self) -> Vec<&'static str> {
750 vec!["core"]
751 }
752
753 fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> {
754 Ok(builder)
756 }
757
758 fn routes(&self) -> Vec<RouteDefinition> {
759 vec![
760 RouteDefinition::new(HttpMethod::POST, "/auth/login", "AuthController::login")
761 .with_middleware(vec!["rate_limit".to_string()])
762 .with_description("User login endpoint"),
763 RouteDefinition::new(HttpMethod::POST, "/auth/logout", "AuthController::logout")
764 .with_middleware(vec!["auth".to_string()])
765 .with_description("User logout endpoint"),
766 ]
767 }
768
769 fn middleware(&self) -> Vec<MiddlewareDefinition> {
770 vec![
771 MiddlewareDefinition::new("auth", 50)
772 .with_description("Authentication middleware"),
773 MiddlewareDefinition::new("rate_limit", 150)
774 .with_description("Rate limiting middleware"),
775 ]
776 }
777
778 fn boot(&self, container: &Container) -> Result<(), ModuleError> {
779 let _config = container.config();
780 Ok(())
782 }
783 }
784
785 #[tokio::test]
786 async fn test_application_with_modules_and_providers() {
787 use crate::provider::ServiceProvider;
788
789 struct TestProvider;
791 impl ServiceProvider for TestProvider {
792 fn name(&self) -> &'static str { "test" }
793
794 fn register(&self, builder: crate::container::ContainerBuilder) -> Result<crate::container::ContainerBuilder, crate::provider::ProviderError> {
795 let config = Arc::new(create_test_config());
796 let database = Arc::new(TestDatabase) as Arc<dyn DatabaseConnection>;
797 Ok(builder.config(config).database(database))
798 }
799 }
800
801 let mut app = Application::builder()
802 .provider(TestProvider)
803 .module(CoreModule)
804 .module(AuthModule)
805 .build()
806 .unwrap();
807
808 let config = app.container().config();
810 assert_eq!(config.environment, Environment::Testing);
811
812 app.start().await.unwrap();
814 }
815
816 #[test]
817 fn test_module_registry_dependency_resolution() {
818 let mut registry = ModuleRegistry::new();
819 registry.register(AuthModule); registry.register(CoreModule); registry.resolve_dependencies().unwrap();
823
824 let loading_order = registry.loading_order();
825
826 let core_pos = loading_order.iter().position(|&i| registry.modules[i].name() == "core").unwrap();
828 let auth_pos = loading_order.iter().position(|&i| registry.modules[i].name() == "auth").unwrap();
829
830 assert!(core_pos < auth_pos);
831 }
832
833 #[test]
834 fn test_module_routes_collection() {
835 let mut registry = ModuleRegistry::new();
836 registry.register(CoreModule);
837 registry.register(AuthModule);
838
839 let routes = registry.collect_routes();
840
841 assert_eq!(routes.len(), 4);
843
844 assert!(routes.iter().any(|r| r.path == "/" && r.method == HttpMethod::GET));
846 assert!(routes.iter().any(|r| r.path == "/health" && r.method == HttpMethod::GET));
847 assert!(routes.iter().any(|r| r.path == "/auth/login" && r.method == HttpMethod::POST));
848 assert!(routes.iter().any(|r| r.path == "/auth/logout" && r.method == HttpMethod::POST));
849 }
850
851 #[test]
852 fn test_module_middleware_collection() {
853 let mut registry = ModuleRegistry::new();
854 registry.register(CoreModule);
855 registry.register(AuthModule);
856
857 let middleware = registry.collect_middleware();
858
859 assert_eq!(middleware.len(), 4);
861
862 assert_eq!(middleware[0].name, "auth"); assert_eq!(middleware[1].name, "logging"); assert_eq!(middleware[2].name, "rate_limit"); assert_eq!(middleware[3].name, "cors"); }
868
869 #[test]
870 fn test_module_missing_dependency() {
871 let mut registry = ModuleRegistry::new();
872 registry.register(AuthModule); let result = registry.resolve_dependencies();
875 assert!(result.is_err());
876
877 if let Err(ModuleError::MissingDependency { module, dependency }) = result {
878 assert_eq!(module, "auth");
879 assert_eq!(dependency, "core");
880 } else {
881 panic!("Expected MissingDependency error");
882 }
883 }
884
885 #[test]
886 fn test_module_circular_dependency() {
887 struct ModuleA;
888 impl Module for ModuleA {
889 fn name(&self) -> &'static str { "a" }
890 fn dependencies(&self) -> Vec<&'static str> { vec!["b"] }
891 fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> { Ok(builder) }
892 }
893
894 struct ModuleB;
895 impl Module for ModuleB {
896 fn name(&self) -> &'static str { "b" }
897 fn dependencies(&self) -> Vec<&'static str> { vec!["a"] }
898 fn configure(&self, builder: ContainerBuilder) -> Result<ContainerBuilder, ModuleError> { Ok(builder) }
899 }
900
901 let mut registry = ModuleRegistry::new();
902 registry.register(ModuleA);
903 registry.register(ModuleB);
904
905 let result = registry.resolve_dependencies();
906 assert!(result.is_err());
907 assert!(matches!(result, Err(ModuleError::CircularDependency { .. })));
908 }
909
910 struct TestLifecycleHook {
912 name: &'static str,
913 executed_phases: Arc<std::sync::Mutex<Vec<String>>>,
914 }
915
916 impl TestLifecycleHook {
917 fn new(name: &'static str) -> (Self, Arc<std::sync::Mutex<Vec<String>>>) {
918 let executed_phases = Arc::new(std::sync::Mutex::new(Vec::new()));
919 let hook = Self {
920 name,
921 executed_phases: executed_phases.clone(),
922 };
923 (hook, executed_phases)
924 }
925 }
926
927 impl LifecycleHook for TestLifecycleHook {
928 fn name(&self) -> &'static str {
929 self.name
930 }
931
932 fn before_start<'life0, 'async_trait>(
933 &'life0 self,
934 _container: &'life0 Container,
935 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
936 where
937 'life0: 'async_trait,
938 Self: 'async_trait,
939 {
940 let phases = self.executed_phases.clone();
941 Box::pin(async move {
942 phases.lock().unwrap().push("before_start".to_string());
943 Ok(())
944 })
945 }
946
947 fn after_start<'life0, 'async_trait>(
948 &'life0 self,
949 _container: &'life0 Container,
950 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
951 where
952 'life0: 'async_trait,
953 Self: 'async_trait,
954 {
955 let phases = self.executed_phases.clone();
956 Box::pin(async move {
957 phases.lock().unwrap().push("after_start".to_string());
958 Ok(())
959 })
960 }
961
962 fn before_stop<'life0, 'async_trait>(
963 &'life0 self,
964 _container: &'life0 Container,
965 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
966 where
967 'life0: 'async_trait,
968 Self: 'async_trait,
969 {
970 let phases = self.executed_phases.clone();
971 Box::pin(async move {
972 phases.lock().unwrap().push("before_stop".to_string());
973 Ok(())
974 })
975 }
976
977 fn after_stop<'life0, 'async_trait>(
978 &'life0 self,
979 _container: &'life0 Container,
980 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
981 where
982 'life0: 'async_trait,
983 Self: 'async_trait,
984 {
985 let phases = self.executed_phases.clone();
986 Box::pin(async move {
987 phases.lock().unwrap().push("after_stop".to_string());
988 Ok(())
989 })
990 }
991 }
992
993 #[tokio::test]
994 async fn test_application_lifecycle() {
995 use crate::provider::ServiceProvider;
996
997 struct TestProvider;
998 impl ServiceProvider for TestProvider {
999 fn name(&self) -> &'static str { "test" }
1000
1001 fn register(&self, builder: crate::container::ContainerBuilder) -> Result<crate::container::ContainerBuilder, crate::provider::ProviderError> {
1002 let config = Arc::new(create_test_config());
1003 let database = Arc::new(TestDatabase) as Arc<dyn DatabaseConnection>;
1004 Ok(builder.config(config).database(database))
1005 }
1006 }
1007
1008 let (hook, phases) = TestLifecycleHook::new("test_hook");
1009
1010 let mut app = Application::builder()
1011 .provider(TestProvider)
1012 .module(CoreModule)
1013 .lifecycle_hook(hook)
1014 .shutdown_timeout(Duration::from_secs(1)) .build()
1016 .unwrap();
1017
1018 assert_eq!(app.state(), &ApplicationState::Created);
1020 assert!(!app.is_running());
1021 assert!(app.uptime().is_none());
1022
1023 app.start().await.unwrap();
1025
1026 assert_eq!(app.state(), &ApplicationState::Running);
1028 assert!(app.is_running());
1029 assert!(app.uptime().is_some());
1030
1031 {
1033 let executed = phases.lock().unwrap();
1034 assert!(executed.contains(&"before_start".to_string()));
1035 assert!(executed.contains(&"after_start".to_string()));
1036 } app.shutdown().await.unwrap();
1040
1041 assert_eq!(app.state(), &ApplicationState::Stopped);
1043 assert!(!app.is_running());
1044
1045 let executed = phases.lock().unwrap();
1047 assert!(executed.contains(&"before_start".to_string()));
1048 assert!(executed.contains(&"after_start".to_string()));
1049 assert!(executed.contains(&"before_stop".to_string()));
1050 assert!(executed.contains(&"after_stop".to_string()));
1051 }
1052
1053 #[tokio::test]
1054 async fn test_application_state_validation() {
1055 use crate::provider::ServiceProvider;
1056
1057 struct TestProvider;
1058 impl ServiceProvider for TestProvider {
1059 fn name(&self) -> &'static str { "test" }
1060 fn register(&self, builder: crate::container::ContainerBuilder) -> Result<crate::container::ContainerBuilder, crate::provider::ProviderError> {
1061 let config = Arc::new(create_test_config());
1062 let database = Arc::new(TestDatabase) as Arc<dyn DatabaseConnection>;
1063 Ok(builder.config(config).database(database))
1064 }
1065 }
1066
1067 let mut app = Application::builder()
1068 .provider(TestProvider)
1069 .build()
1070 .unwrap();
1071
1072 let result = app.shutdown().await;
1074 assert!(result.is_err());
1075 assert!(matches!(result, Err(ApplicationError::InvalidState { .. })));
1076
1077 app.start().await.unwrap();
1079
1080 let result = app.start().await;
1082 assert!(result.is_err());
1083 assert!(matches!(result, Err(ApplicationError::InvalidState { .. })));
1084 }
1085
1086 #[tokio::test]
1087 async fn test_failed_lifecycle_hook() {
1088 use crate::provider::ServiceProvider;
1089
1090 struct FailingHook;
1091 impl LifecycleHook for FailingHook {
1092 fn name(&self) -> &'static str { "failing_hook" }
1093
1094 fn before_start<'life0, 'async_trait>(
1095 &'life0 self,
1096 _container: &'life0 Container,
1097 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>> + Send + 'async_trait>>
1098 where
1099 'life0: 'async_trait,
1100 Self: 'async_trait,
1101 {
1102 Box::pin(async move {
1103 Err("Hook failed".into())
1104 })
1105 }
1106 }
1107
1108 struct TestProvider;
1109 impl ServiceProvider for TestProvider {
1110 fn name(&self) -> &'static str { "test" }
1111 fn register(&self, builder: crate::container::ContainerBuilder) -> Result<crate::container::ContainerBuilder, crate::provider::ProviderError> {
1112 let config = Arc::new(create_test_config());
1113 let database = Arc::new(TestDatabase) as Arc<dyn DatabaseConnection>;
1114 Ok(builder.config(config).database(database))
1115 }
1116 }
1117
1118 let mut app = Application::builder()
1119 .provider(TestProvider)
1120 .lifecycle_hook(FailingHook)
1121 .build()
1122 .unwrap();
1123
1124 let result = app.start().await;
1125 assert!(result.is_err());
1126 assert!(matches!(result, Err(ApplicationError::LifecycleHookFailed { .. })));
1127 assert!(matches!(app.state(), ApplicationState::Failed(_)));
1128 }
1129
1130 #[tokio::test]
1131 async fn test_full_application_with_modules() {
1132 use crate::provider::ServiceProvider;
1133
1134 struct TestProvider;
1136 impl ServiceProvider for TestProvider {
1137 fn name(&self) -> &'static str { "test" }
1138
1139 fn register(&self, builder: crate::container::ContainerBuilder) -> Result<crate::container::ContainerBuilder, crate::provider::ProviderError> {
1140 let config = Arc::new(create_test_config());
1141 let database = Arc::new(TestDatabase) as Arc<dyn DatabaseConnection>;
1142 Ok(builder.config(config).database(database))
1143 }
1144 }
1145
1146 let mut app = Application::builder()
1147 .provider(TestProvider)
1148 .module(CoreModule)
1149 .module(AuthModule)
1150 .build()
1151 .unwrap();
1152
1153 let config = app.container().config();
1155 assert_eq!(config.environment, Environment::Testing);
1156
1157 let routes = app.routes();
1159 assert_eq!(routes.len(), 4);
1160
1161 let middleware = app.middleware();
1163 assert_eq!(middleware.len(), 4);
1164 assert_eq!(middleware[0].name, "auth"); app.start().await.unwrap();
1168
1169 assert!(app.is_running());
1171 assert!(app.uptime().is_some());
1172
1173 app.shutdown().await.unwrap();
1175
1176 assert!(!app.is_running());
1178 assert_eq!(app.state(), &ApplicationState::Stopped);
1179 }
1180}