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}