subx_cli/core/services.rs
1//! Service container for dependency management and injection.
2//!
3//! This module provides a centralized service container that manages
4//! the lifecycle of services and components, enabling clean dependency
5//! injection throughout the application.
6
7use crate::{Result, config::ConfigService, core::ComponentFactory};
8use std::sync::Arc;
9
10/// Service container for dependency injection and service management.
11///
12/// The service container holds references to core services and provides
13/// a centralized way to access them throughout the application. It manages
14/// the lifecycle of services and ensures proper dependency injection.
15///
16/// # Design Principles
17///
18/// - **Single Source of Truth**: All services are managed through the container
19/// - **Dependency Injection**: Components receive dependencies explicitly
20/// - **Configuration Isolation**: Services are decoupled from global configuration
21/// - **Test Friendliness**: Easy to mock and test individual components
22///
23/// # Examples
24///
25/// ```rust
26/// use subx_cli::core::ServiceContainer;
27/// use subx_cli::config::ProductionConfigService;
28/// use std::sync::Arc;
29///
30/// # async fn example() -> subx_cli::Result<()> {
31/// let config_service = Arc::new(ProductionConfigService::new()?);
32/// let container = ServiceContainer::new(config_service)?;
33///
34/// // Access services through container
35/// let config_service = container.config_service();
36/// let factory = container.component_factory();
37/// # Ok(())
38/// # }
39/// ```
40pub struct ServiceContainer {
41 config_service: Arc<dyn ConfigService>,
42 component_factory: ComponentFactory,
43}
44
45/// Sync detection service factory for creating different sync detectors.
46///
47/// This factory creates various sync detection services with the provided
48/// configuration service for dependency injection.
49pub struct SyncServiceFactory {
50 config_service: Box<dyn ConfigService>,
51}
52
53impl SyncServiceFactory {
54 /// Create a new sync service factory.
55 ///
56 /// # Arguments
57 ///
58 /// * `config_service` - Configuration service for dependency injection
59 pub fn new(config_service: Box<dyn ConfigService>) -> Self {
60 Self { config_service }
61 }
62}
63
64impl ServiceContainer {
65 /// Create a new service container with the given configuration service.
66 ///
67 /// # Arguments
68 ///
69 /// * `config_service` - Configuration service implementation
70 ///
71 /// # Errors
72 ///
73 /// Returns an error if component factory creation fails.
74 pub fn new(config_service: Arc<dyn ConfigService>) -> Result<Self> {
75 let component_factory = ComponentFactory::new(config_service.as_ref())?;
76
77 Ok(Self {
78 config_service,
79 component_factory,
80 })
81 }
82
83 /// Get a reference to the configuration service.
84 ///
85 /// Returns a reference to the configuration service managed by this container.
86 pub fn config_service(&self) -> &Arc<dyn ConfigService> {
87 &self.config_service
88 }
89
90 /// Get a reference to the component factory.
91 ///
92 /// Returns a reference to the component factory that can create
93 /// configured instances of core components.
94 pub fn component_factory(&self) -> &ComponentFactory {
95 &self.component_factory
96 }
97
98 /// Reload all services and components.
99 ///
100 /// This method triggers a reload of the configuration service and
101 /// recreates the component factory with the updated configuration.
102 /// This is useful for dynamic configuration updates.
103 ///
104 /// # Errors
105 ///
106 /// Returns an error if configuration reloading or factory recreation fails.
107 pub fn reload(&mut self) -> Result<()> {
108 // Reload configuration service
109 self.config_service.reload()?;
110
111 // Recreate component factory with updated configuration
112 self.component_factory = ComponentFactory::new(self.config_service.as_ref())?;
113
114 Ok(())
115 }
116
117 /// Create a new service container for testing with custom configuration.
118 ///
119 /// This method is useful for testing scenarios where you need to provide
120 /// specific configuration values.
121 ///
122 /// # Arguments
123 ///
124 /// * `config_service` - Test configuration service
125 ///
126 /// # Errors
127 ///
128 /// Returns an error if container creation fails.
129 #[cfg(test)]
130 pub fn new_for_testing(config_service: Arc<dyn ConfigService>) -> Result<Self> {
131 Self::new(config_service)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::config::test_service::TestConfigService;
139
140 #[test]
141 fn test_service_container_creation() {
142 let config_service = Arc::new(TestConfigService::default());
143 let container = ServiceContainer::new(config_service);
144 assert!(container.is_ok());
145 }
146
147 #[test]
148 fn test_service_container_access() {
149 let config_service = Arc::new(TestConfigService::default());
150 let container = ServiceContainer::new(config_service.clone()).unwrap();
151
152 // Test that we can access services
153 let _retrieved_config_service = container.config_service();
154 // Note: Can't test pointer equality due to trait object casting
155
156 let factory = container.component_factory();
157 assert!(factory.config().ai.provider == "openai");
158 }
159
160 #[test]
161 fn test_service_container_reload() {
162 let config_service = Arc::new(TestConfigService::default());
163 let mut container = ServiceContainer::new(config_service).unwrap();
164
165 // Test reload operation
166 let result = container.reload();
167 assert!(result.is_ok());
168 }
169
170 #[test]
171 fn test_new_for_testing() {
172 let config_service = Arc::new(TestConfigService::default());
173 let container = ServiceContainer::new_for_testing(config_service);
174 assert!(container.is_ok());
175 }
176}