subx_cli/config/mod.rs
1// src/config/mod.rs
2//! Configuration management module for SubX.
3//!
4//! This module provides the complete configuration service system with
5//! dependency injection support and comprehensive type definitions.
6//!
7//! # Key Components
8//!
9//! - [`Config`] - Main configuration structure containing all settings
10//! - [`ConfigService`] - Service interface for configuration management
11//! - [`ProductionConfigService`] - Production implementation with file I/O
12//! - [`TestConfigService`] - Test implementation with controlled behavior
13//! - [`TestConfigBuilder`] - Builder pattern for test configurations
14//!
15//! # Examples
16//!
17//! ```rust
18//! use subx_cli::config::{Config, ConfigService, ProductionConfigService};
19//!
20//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! // Create a production configuration service
22//! let config_service = ProductionConfigService::new()?;
23//!
24//! // Load configuration
25//! let config = config_service.get_config()?;
26//! println!("AI Provider: {}", config.ai.provider);
27//! # Ok(())
28//! # }
29//! ```
30//!
31//! # Architecture
32//!
33//! The configuration system uses dependency injection to provide testable
34//! and maintainable configuration management. All configuration access
35//! should go through the [`ConfigService`] trait.
36
37use serde::{Deserialize, Serialize};
38use std::path::PathBuf;
39
40// Configuration service system
41pub mod builder;
42pub mod environment;
43pub mod service;
44pub mod test_macros;
45pub mod test_service;
46pub mod validator;
47
48// ============================================================================
49// Configuration Type Definitions
50// ============================================================================
51
52/// Full application configuration for SubX.
53///
54/// This struct aggregates all settings for AI integration, subtitle format
55/// conversion, synchronization, general options, and parallel execution.
56///
57/// # Examples
58///
59/// ```rust
60/// use subx_cli::config::Config;
61///
62/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
63/// let config = Config::default();
64/// assert_eq!(config.ai.provider, "openai");
65/// assert_eq!(config.formats.default_output, "srt");
66/// # Ok(())
67/// # }
68/// ```
69///
70/// # Serialization
71///
72/// This struct can be serialized to/from TOML format for configuration files.
73///
74/// ```rust
75/// use subx_cli::config::Config;
76///
77/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
78/// let config = Config::default();
79/// let toml_str = toml::to_string(&config)?;
80/// assert!(toml_str.contains("[ai]"));
81/// # Ok(())
82/// # }
83/// ```
84#[derive(Debug, Serialize, Deserialize, Clone, Default)]
85pub struct Config {
86 /// AI service configuration parameters.
87 pub ai: AIConfig,
88 /// Subtitle format conversion settings.
89 pub formats: FormatsConfig,
90 /// Audio-subtitle synchronization options.
91 pub sync: SyncConfig,
92 /// General runtime options (e.g., backup enabled, job limits).
93 pub general: GeneralConfig,
94 /// Parallel processing parameters.
95 pub parallel: ParallelConfig,
96 /// Optional file path from which the configuration was loaded.
97 pub loaded_from: Option<PathBuf>,
98}
99
100/// AI service provider configuration.
101///
102/// Contains all settings required for AI provider integration,
103/// including authentication, model selection, and retry behavior.
104///
105/// # Examples
106///
107/// ```rust
108/// use subx_cli::config::AIConfig;
109///
110/// let ai_config = AIConfig::default();
111/// assert_eq!(ai_config.provider, "openai");
112/// assert_eq!(ai_config.model, "gpt-4.1-mini");
113/// assert_eq!(ai_config.temperature, 0.3);
114/// ```
115#[derive(Debug, Serialize, Deserialize, Clone)]
116pub struct AIConfig {
117 /// AI provider name (e.g. "openai", "anthropic").
118 pub provider: String,
119 /// API key for authentication.
120 pub api_key: Option<String>,
121 /// AI model name to use.
122 pub model: String,
123 /// API base URL.
124 pub base_url: String,
125 /// Maximum sample length per request.
126 pub max_sample_length: usize,
127 /// AI generation creativity parameter (0.0-1.0).
128 pub temperature: f32,
129 /// Number of retries on request failure.
130 pub retry_attempts: u32,
131 /// Retry interval in milliseconds.
132 pub retry_delay_ms: u64,
133}
134
135impl Default for AIConfig {
136 fn default() -> Self {
137 Self {
138 provider: "openai".to_string(),
139 api_key: None,
140 model: "gpt-4.1-mini".to_string(),
141 base_url: "https://api.openai.com/v1".to_string(),
142 max_sample_length: 3000,
143 temperature: 0.3,
144 retry_attempts: 3,
145 retry_delay_ms: 1000,
146 }
147 }
148}
149
150/// Subtitle format related configuration.
151///
152/// Controls how subtitle files are processed, including format conversion,
153/// encoding detection, and style preservation.
154///
155/// # Examples
156///
157/// ```rust
158/// use subx_cli::config::FormatsConfig;
159///
160/// let formats = FormatsConfig::default();
161/// assert_eq!(formats.default_output, "srt");
162/// assert_eq!(formats.default_encoding, "utf-8");
163/// assert!(!formats.preserve_styling);
164/// ```
165#[derive(Debug, Serialize, Deserialize, Clone)]
166pub struct FormatsConfig {
167 /// Default output format (e.g. "srt", "ass", "vtt").
168 pub default_output: String,
169 /// Whether to preserve style information during format conversion.
170 pub preserve_styling: bool,
171 /// Default character encoding (e.g. "utf-8", "gbk").
172 pub default_encoding: String,
173 /// Encoding detection confidence threshold (0.0-1.0).
174 pub encoding_detection_confidence: f32,
175}
176
177impl Default for FormatsConfig {
178 fn default() -> Self {
179 Self {
180 default_output: "srt".to_string(),
181 preserve_styling: false,
182 default_encoding: "utf-8".to_string(),
183 encoding_detection_confidence: 0.8,
184 }
185 }
186}
187
188/// Audio synchronization related configuration.
189///
190/// Contains settings for audio-subtitle synchronization operations,
191/// including offset limits, sample rates, and dialogue detection.
192///
193/// # Examples
194///
195/// ```rust
196/// use subx_cli::config::SyncConfig;
197///
198/// let sync = SyncConfig::default();
199/// assert_eq!(sync.max_offset_seconds, 10.0);
200/// assert_eq!(sync.audio_sample_rate, 44100);
201/// assert!(sync.enable_dialogue_detection);
202/// ```
203#[derive(Debug, Serialize, Deserialize, Clone)]
204pub struct SyncConfig {
205 /// Maximum offset in seconds for synchronization.
206 pub max_offset_seconds: f32,
207 /// Audio sample rate for processing.
208 pub audio_sample_rate: u32,
209 /// Correlation threshold for sync quality (0.0-1.0).
210 pub correlation_threshold: f32,
211 /// Dialogue detection threshold (0.0-1.0).
212 pub dialogue_detection_threshold: f32,
213 /// Minimum dialogue duration in milliseconds.
214 pub min_dialogue_duration_ms: u32,
215 /// Gap between dialogues for merging (milliseconds).
216 pub dialogue_merge_gap_ms: u32,
217 /// Enable dialogue detection.
218 pub enable_dialogue_detection: bool,
219 /// Auto-detect sample rate from audio files.
220 pub auto_detect_sample_rate: bool,
221}
222
223impl Default for SyncConfig {
224 fn default() -> Self {
225 Self {
226 max_offset_seconds: 10.0,
227 audio_sample_rate: 44100,
228 correlation_threshold: 0.8,
229 dialogue_detection_threshold: 0.6,
230 min_dialogue_duration_ms: 500,
231 dialogue_merge_gap_ms: 200,
232 enable_dialogue_detection: true,
233 auto_detect_sample_rate: true,
234 }
235 }
236}
237
238/// General application configuration.
239///
240/// Contains general runtime options that affect the overall behavior
241/// of the application, including backup settings, job limits, and logging.
242///
243/// # Examples
244///
245/// ```rust
246/// use subx_cli::config::GeneralConfig;
247///
248/// let general = GeneralConfig::default();
249/// assert!(!general.backup_enabled);
250/// assert_eq!(general.max_concurrent_jobs, 4);
251/// assert_eq!(general.log_level, "info");
252/// ```
253#[derive(Debug, Serialize, Deserialize, Clone)]
254pub struct GeneralConfig {
255 /// Enable automatic backup of original files.
256 pub backup_enabled: bool,
257 /// Maximum number of concurrent processing jobs.
258 pub max_concurrent_jobs: usize,
259 /// Temporary directory for processing.
260 pub temp_dir: Option<PathBuf>,
261 /// Log level for application output.
262 pub log_level: String,
263 /// Cache directory for storing processed data.
264 pub cache_dir: Option<PathBuf>,
265 /// Task timeout in seconds.
266 pub task_timeout_seconds: u64,
267 /// Enable progress bar display.
268 pub enable_progress_bar: bool,
269 /// Worker idle timeout in seconds.
270 pub worker_idle_timeout_seconds: u64,
271}
272
273impl Default for GeneralConfig {
274 fn default() -> Self {
275 Self {
276 backup_enabled: false,
277 max_concurrent_jobs: 4,
278 temp_dir: None,
279 log_level: "info".to_string(),
280 cache_dir: None,
281 task_timeout_seconds: 300,
282 enable_progress_bar: true,
283 worker_idle_timeout_seconds: 60,
284 }
285 }
286}
287
288/// Parallel processing configuration.
289///
290/// Controls how parallel processing is performed, including worker
291/// management, task distribution, and overflow handling strategies.
292///
293/// # Examples
294///
295/// ```rust
296/// use subx_cli::config::{ParallelConfig, OverflowStrategy};
297///
298/// let parallel = ParallelConfig::default();
299/// assert!(parallel.max_workers > 0);
300/// assert_eq!(parallel.overflow_strategy, OverflowStrategy::Block);
301/// assert!(parallel.enable_work_stealing);
302/// ```
303#[derive(Debug, Serialize, Deserialize, Clone)]
304pub struct ParallelConfig {
305 /// Maximum number of worker threads.
306 pub max_workers: usize,
307 /// Chunk size for parallel processing.
308 pub chunk_size: usize,
309 /// Overflow strategy when workers are busy.
310 pub overflow_strategy: OverflowStrategy,
311 /// Enable work stealing between workers.
312 pub enable_work_stealing: bool,
313 /// Task queue size.
314 pub task_queue_size: usize,
315 /// Enable task priorities.
316 pub enable_task_priorities: bool,
317 /// Auto-balance workers.
318 pub auto_balance_workers: bool,
319}
320
321impl Default for ParallelConfig {
322 fn default() -> Self {
323 Self {
324 max_workers: num_cpus::get(),
325 chunk_size: 1000,
326 overflow_strategy: OverflowStrategy::Block,
327 enable_work_stealing: true,
328 task_queue_size: 1000,
329 enable_task_priorities: false,
330 auto_balance_workers: true,
331 }
332 }
333}
334
335/// Strategy for handling overflow when all workers are busy.
336///
337/// This enum defines different strategies for handling situations where
338/// all worker threads are occupied and new tasks arrive.
339///
340/// # Examples
341///
342/// ```rust
343/// use subx_cli::config::OverflowStrategy;
344///
345/// let strategy = OverflowStrategy::Block;
346/// assert_eq!(strategy, OverflowStrategy::Block);
347///
348/// // Comparison and serialization
349/// let strategies = vec![
350/// OverflowStrategy::Block,
351/// OverflowStrategy::Drop,
352/// OverflowStrategy::Expand,
353/// ];
354/// assert_eq!(strategies.len(), 3);
355/// ```
356#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
357pub enum OverflowStrategy {
358 /// Block until a worker becomes available.
359 ///
360 /// This is the safest option as it ensures all tasks are processed,
361 /// but may cause the application to become unresponsive.
362 Block,
363 /// Drop new tasks when all workers are busy.
364 ///
365 /// Use this when task loss is acceptable and responsiveness is critical.
366 Drop,
367 /// Create additional temporary workers.
368 ///
369 /// This can help with load spikes but may consume excessive resources.
370 Expand,
371 /// Drop oldest tasks in queue.
372 ///
373 /// Prioritizes recent tasks over older ones in the queue.
374 DropOldest,
375 /// Reject new tasks.
376 ///
377 /// Similar to Drop but may provide error feedback to the caller.
378 Reject,
379}
380
381// ============================================================================
382// Configuration Tests
383// ============================================================================
384
385#[cfg(test)]
386mod config_tests {
387 use super::*;
388
389 #[test]
390 fn test_default_config_creation() {
391 let config = Config::default();
392 assert_eq!(config.ai.provider, "openai");
393 assert_eq!(config.ai.model, "gpt-4.1-mini");
394 assert_eq!(config.formats.default_output, "srt");
395 assert!(!config.general.backup_enabled);
396 assert_eq!(config.general.max_concurrent_jobs, 4);
397 }
398
399 #[test]
400 fn test_ai_config_defaults() {
401 let ai_config = AIConfig::default();
402 assert_eq!(ai_config.provider, "openai");
403 assert_eq!(ai_config.model, "gpt-4.1-mini");
404 assert_eq!(ai_config.temperature, 0.3);
405 assert_eq!(ai_config.max_sample_length, 3000);
406 }
407
408 #[test]
409 fn test_sync_config_defaults() {
410 let sync_config = SyncConfig::default();
411 assert_eq!(sync_config.max_offset_seconds, 10.0);
412 assert_eq!(sync_config.correlation_threshold, 0.8);
413 assert_eq!(sync_config.audio_sample_rate, 44100);
414 assert!(sync_config.enable_dialogue_detection);
415 }
416
417 #[test]
418 fn test_config_serialization() {
419 let config = Config::default();
420 let toml_str = toml::to_string(&config).unwrap();
421 assert!(toml_str.contains("[ai]"));
422 assert!(toml_str.contains("[sync]"));
423 assert!(toml_str.contains("[general]"));
424 assert!(toml_str.contains("[parallel]"));
425 }
426
427 #[test]
428 fn test_overflow_strategy_equality() {
429 assert_eq!(OverflowStrategy::Block, OverflowStrategy::Block);
430 assert_ne!(OverflowStrategy::Block, OverflowStrategy::Drop);
431 }
432}
433
434// ============================================================================
435// Public API Re-exports
436// ============================================================================
437
438// Re-export the configuration service system
439pub use builder::TestConfigBuilder;
440pub use environment::{EnvironmentProvider, SystemEnvironmentProvider, TestEnvironmentProvider};
441pub use service::{ConfigService, ProductionConfigService};
442pub use test_service::TestConfigService;