subx_cli/core/factory.rs
1//! Component factory for creating configured instances of core components.
2//!
3//! This module provides a centralized factory for creating instances of core
4//! components with proper configuration injection, eliminating the need for
5//! global configuration access within individual components.
6
7use crate::{
8 Result,
9 config::{Config, ConfigService},
10 core::{
11 file_manager::FileManager, matcher::engine::MatchEngine,
12 sync::dialogue::detector::DialogueDetector,
13 },
14 error::SubXError,
15 services::ai::AIProvider,
16};
17
18/// Component factory for creating configured instances.
19///
20/// This factory provides a centralized way to create core components
21/// with proper configuration injection, ensuring consistent component
22/// initialization across the application.
23///
24/// # Examples
25///
26/// ```rust
27/// use subx_cli::core::ComponentFactory;
28/// use subx_cli::config::ProductionConfigService;
29/// use std::sync::Arc;
30///
31/// # async fn example() -> subx_cli::Result<()> {
32/// let config_service = Arc::new(ProductionConfigService::new()?);
33/// let factory = ComponentFactory::new(config_service.as_ref())?;
34///
35/// // Create components with proper configuration
36/// let detector = factory.create_dialogue_detector();
37/// let match_engine = factory.create_match_engine()?;
38/// let file_manager = factory.create_file_manager();
39/// # Ok(())
40/// # }
41/// ```
42pub struct ComponentFactory {
43 config: Config,
44}
45
46impl ComponentFactory {
47 /// Create a new component factory with the given configuration service.
48 ///
49 /// # Arguments
50 ///
51 /// * `config_service` - Configuration service to load configuration from
52 ///
53 /// # Errors
54 ///
55 /// Returns an error if configuration loading fails.
56 pub fn new(config_service: &dyn ConfigService) -> Result<Self> {
57 let config = config_service.get_config()?;
58 Ok(Self { config })
59 }
60
61 /// Create a dialogue detector with sync configuration.
62 ///
63 /// Returns a properly configured DialogueDetector instance using
64 /// the sync configuration section.
65 pub fn create_dialogue_detector(&self) -> DialogueDetector {
66 DialogueDetector::new(&self.config.sync)
67 }
68
69 /// Create a match engine with AI configuration.
70 ///
71 /// Returns a properly configured MatchEngine instance using
72 /// the AI configuration section.
73 ///
74 /// # Errors
75 ///
76 /// Returns an error if AI provider creation fails.
77 pub fn create_match_engine(&self) -> Result<MatchEngine> {
78 let ai_provider = self.create_ai_provider()?;
79 let match_config = crate::core::matcher::MatchConfig {
80 confidence_threshold: 0.8, // Default value, can be configurable
81 max_sample_length: self.config.ai.max_sample_length,
82 enable_content_analysis: true,
83 backup_enabled: self.config.general.backup_enabled,
84 relocation_mode: crate::core::matcher::engine::FileRelocationMode::None,
85 conflict_resolution: crate::core::matcher::engine::ConflictResolution::AutoRename,
86 };
87 Ok(MatchEngine::new(ai_provider, match_config))
88 }
89
90 /// Create a file manager with general configuration.
91 ///
92 /// Returns a properly configured FileManager instance using
93 /// the general configuration section.
94 pub fn create_file_manager(&self) -> FileManager {
95 // For now, FileManager doesn't take configuration in its constructor
96 // This will be updated when FileManager is refactored to accept config
97 FileManager::new()
98 }
99
100 /// Create an AI provider with AI configuration.
101 ///
102 /// Returns a properly configured AI provider instance based on
103 /// the provider type specified in the AI configuration.
104 ///
105 /// # Errors
106 ///
107 /// Returns an error if the provider type is unsupported or
108 /// provider creation fails.
109 pub fn create_ai_provider(&self) -> Result<Box<dyn AIProvider>> {
110 create_ai_provider(&self.config.ai)
111 }
112
113 /// Get a reference to the current configuration.
114 ///
115 /// Returns a reference to the configuration used by this factory.
116 pub fn config(&self) -> &Config {
117 &self.config
118 }
119}
120
121/// Create an AI provider from AI configuration.
122///
123/// This function creates the appropriate AI provider based on the
124/// provider type specified in the configuration.
125///
126/// # Arguments
127///
128/// * `ai_config` - AI configuration containing provider settings
129///
130/// # Errors
131///
132/// Returns an error if the provider type is unsupported or creation fails.
133pub fn create_ai_provider(ai_config: &crate::config::AIConfig) -> Result<Box<dyn AIProvider>> {
134 match ai_config.provider.as_str() {
135 "openai" => {
136 // For now, just create a mock provider since the actual implementation might not be ready
137 Err(SubXError::config(
138 "AI provider creation not yet implemented",
139 ))
140 }
141 _ => Err(SubXError::config(format!(
142 "Unsupported AI provider: {}",
143 ai_config.provider
144 ))),
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::config::test_service::TestConfigService;
152
153 #[test]
154 fn test_component_factory_creation() {
155 let config_service = TestConfigService::default();
156 let factory = ComponentFactory::new(&config_service);
157 assert!(factory.is_ok());
158 }
159
160 #[test]
161 fn test_create_dialogue_detector() {
162 let config_service = TestConfigService::default();
163 let factory = ComponentFactory::new(&config_service).unwrap();
164
165 let detector = factory.create_dialogue_detector();
166 // Basic validation that detector was created with config
167 assert!(detector.config().correlation_threshold > 0.0);
168 }
169
170 #[test]
171 fn test_create_file_manager() {
172 let config_service = TestConfigService::default();
173 let factory = ComponentFactory::new(&config_service).unwrap();
174
175 let _file_manager = factory.create_file_manager();
176 // Basic validation that file manager was created
177 // FileManager doesn't expose config yet, so just verify creation succeeds
178 }
179
180 #[test]
181 fn test_unsupported_ai_provider() {
182 let mut config = crate::config::Config::default();
183 config.ai.provider = "unsupported".to_string();
184
185 let result: Result<Box<dyn AIProvider>> = create_ai_provider(&config.ai);
186 assert!(result.is_err());
187
188 match result {
189 Err(e) => {
190 let error_msg = e.to_string();
191 assert!(error_msg.contains("Unsupported AI provider"));
192 }
193 Ok(_) => panic!("Expected error for unsupported provider"),
194 }
195 }
196}