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;