subx_cli/config/mod.rs
1// src/config/mod.rs
2#![allow(deprecated)]
3//! Configuration management module for SubX.
4//!
5//! This module provides the complete configuration service system with
6//! dependency injection support and comprehensive type definitions.
7//!
8//! # Key Components
9//!
10//! - [`Config`] - Main configuration structure containing all settings
11//! - [`ConfigService`] - Service interface for configuration management
12//! - [`ProductionConfigService`] - Production implementation with file I/O
13//! - [`TestConfigService`] - Test implementation with controlled behavior
14//! - [`TestConfigBuilder`] - Builder pattern for test configurations
15//!
16//! # Validation System
17//!
18//! The configuration system provides a layered validation architecture:
19//!
20//! - [`validation`] - Low-level validation functions for individual values
21//! - [`validator`] - High-level configuration section validators
22//! - [`field_validator`] - Key-value validation for configuration service
23//!
24//! ## Architecture
25//!
26//! ```text
27//! ConfigService
28//! ↓
29//! field_validator (key-value validation)
30//! ↓
31//! validation (primitive validation functions)
32//!
33//! validator (section validation)
34//! ↓
35//! validation (primitive validation functions)
36//! ```
37//!
38//! # Examples
39//!
40//! ```rust
41//! use subx_cli::config::{Config, ConfigService, ProductionConfigService};
42//!
43//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
44//! // Create a production configuration service
45//! let config_service = ProductionConfigService::new()?;
46//!
47//! // Load configuration
48//! let config = config_service.get_config()?;
49//! println!("AI Provider: {}", config.ai.provider);
50//! # Ok(())
51//! # }
52//! ```
53//!
54//! # Architecture
55//!
56//! The configuration system uses dependency injection to provide testable
57//! and maintainable configuration management. All configuration access
58//! should go through the [`ConfigService`] trait.
59
60use serde::{Deserialize, Serialize};
61use std::path::PathBuf;
62
63// Configuration service system
64pub mod builder;
65pub mod environment;
66pub mod field_validator;
67pub mod service;
68pub mod test_macros;
69pub mod test_service;
70pub mod validation;
71pub mod validator;
72
73// ============================================================================
74// Configuration Type Definitions
75// ============================================================================
76
77/// Full application configuration for SubX.
78///
79/// This struct aggregates all settings for AI integration, subtitle format
80/// conversion, synchronization, general options, and parallel execution.
81///
82/// # Examples
83///
84/// ```rust
85/// use subx_cli::config::Config;
86///
87/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
88/// let config = Config::default();
89/// assert_eq!(config.ai.provider, "openai");
90/// assert_eq!(config.formats.default_output, "srt");
91/// # Ok(())
92/// # }
93/// ```
94///
95/// # Serialization
96///
97/// This struct can be serialized to/from TOML format for configuration files.
98///
99/// ```rust
100/// use subx_cli::config::Config;
101///
102/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
103/// let config = Config::default();
104/// let toml_str = toml::to_string(&config)?;
105/// assert!(toml_str.contains("[ai]"));
106/// # Ok(())
107/// # }
108/// ```
109#[derive(Debug, Serialize, Deserialize, Clone, Default)]
110pub struct Config {
111 /// AI service configuration parameters.
112 pub ai: AIConfig,
113 /// Subtitle format conversion settings.
114 pub formats: FormatsConfig,
115 /// Audio-subtitle synchronization options.
116 pub sync: SyncConfig,
117 /// General runtime options (e.g., backup enabled, job limits).
118 pub general: GeneralConfig,
119 /// Parallel processing parameters.
120 pub parallel: ParallelConfig,
121 /// Optional file path from which the configuration was loaded.
122 pub loaded_from: Option<PathBuf>,
123}
124
125/// AI service configuration parameters.
126///
127/// This structure defines all configuration options for AI providers,
128/// including authentication, model parameters, retry behavior, and timeouts.
129///
130/// # Examples
131///
132/// Creating a default configuration:
133/// ```rust
134/// use subx_cli::config::AIConfig;
135///
136/// let ai_config = AIConfig::default();
137/// assert_eq!(ai_config.provider, "openai");
138/// assert_eq!(ai_config.model, "gpt-4.1-mini");
139/// assert_eq!(ai_config.temperature, 0.3);
140/// ```
141#[derive(Debug, Serialize, Deserialize, Clone)]
142pub struct AIConfig {
143 /// AI provider name (e.g. "openai", "anthropic").
144 pub provider: String,
145 /// API key for authentication.
146 pub api_key: Option<String>,
147 /// AI model name to use.
148 pub model: String,
149 /// API base URL.
150 pub base_url: String,
151 /// Maximum sample length per request.
152 pub max_sample_length: usize,
153 /// AI generation creativity parameter (0.0-1.0).
154 pub temperature: f32,
155 /// Maximum tokens in response.
156 pub max_tokens: u32,
157 /// Number of retries on request failure.
158 pub retry_attempts: u32,
159 /// Retry interval in milliseconds.
160 pub retry_delay_ms: u64,
161 /// HTTP request timeout in seconds.
162 /// This controls how long to wait for a response from the AI service.
163 /// For slow networks or complex requests, you may need to increase this value.
164 pub request_timeout_seconds: u64,
165
166 /// Azure OpenAI API version (optional, defaults to latest)
167 #[serde(default)]
168 pub api_version: Option<String>,
169}
170
171impl Default for AIConfig {
172 fn default() -> Self {
173 Self {
174 provider: "openai".to_string(),
175 api_key: None,
176 model: "gpt-4.1-mini".to_string(),
177 base_url: "https://api.openai.com/v1".to_string(),
178 max_sample_length: 3000,
179 temperature: 0.3,
180 max_tokens: 10000,
181 retry_attempts: 3,
182 retry_delay_ms: 1000,
183 // Set to 120 seconds to handle slow networks and complex AI requests
184 // This is especially important for users with high-latency connections
185 request_timeout_seconds: 120,
186 api_version: None,
187 }
188 }
189}
190
191/// Subtitle format related configuration.
192///
193/// Controls how subtitle files are processed, including format conversion,
194/// encoding detection, and style preservation.
195///
196/// # Examples
197///
198/// ```rust
199/// use subx_cli::config::FormatsConfig;
200///
201/// let formats = FormatsConfig::default();
202/// assert_eq!(formats.default_output, "srt");
203/// assert_eq!(formats.default_encoding, "utf-8");
204/// assert!(!formats.preserve_styling);
205/// ```
206#[derive(Debug, Serialize, Deserialize, Clone)]
207pub struct FormatsConfig {
208 /// Default output format (e.g. "srt", "ass", "vtt").
209 pub default_output: String,
210 /// Whether to preserve style information during format conversion.
211 pub preserve_styling: bool,
212 /// Default character encoding (e.g. "utf-8", "gbk").
213 pub default_encoding: String,
214 /// Encoding detection confidence threshold (0.0-1.0).
215 pub encoding_detection_confidence: f32,
216}
217
218impl Default for FormatsConfig {
219 fn default() -> Self {
220 Self {
221 default_output: "srt".to_string(),
222 preserve_styling: false,
223 default_encoding: "utf-8".to_string(),
224 encoding_detection_confidence: 0.8,
225 }
226 }
227}
228
229/// Audio synchronization configuration supporting VAD speech detection.
230///
231/// This configuration struct defines settings for subtitle-audio synchronization,
232/// including method selection, timing constraints, and VAD-specific parameters.
233#[derive(Debug, Serialize, Deserialize, Clone)]
234pub struct SyncConfig {
235 /// Default synchronization method ("vad", "auto")
236 pub default_method: String,
237 /// Maximum allowed time offset in seconds
238 pub max_offset_seconds: f32,
239 /// Local VAD related settings
240 pub vad: VadConfig,
241
242 // Deprecated legacy fields, preserved for backward compatibility
243 /// Deprecated: correlation threshold for audio analysis
244 #[deprecated]
245 #[serde(skip)]
246 pub correlation_threshold: f32,
247 /// Deprecated: dialogue detection threshold
248 #[deprecated]
249 #[serde(skip)]
250 pub dialogue_detection_threshold: f32,
251 /// Deprecated: minimum dialogue duration in milliseconds
252 #[deprecated]
253 #[serde(skip)]
254 pub min_dialogue_duration_ms: u32,
255 /// Deprecated: dialogue merge gap in milliseconds
256 #[deprecated]
257 #[serde(skip)]
258 pub dialogue_merge_gap_ms: u32,
259 /// Deprecated: enable dialogue detection flag
260 #[deprecated]
261 #[serde(skip)]
262 pub enable_dialogue_detection: bool,
263 /// Deprecated: audio sample rate
264 #[deprecated]
265 #[serde(skip)]
266 pub audio_sample_rate: u32,
267 /// Deprecated: auto-detect sample rate flag
268 #[deprecated]
269 #[serde(skip)]
270 pub auto_detect_sample_rate: bool,
271}
272
273/// Local Voice Activity Detection configuration.
274///
275/// This struct defines parameters for local VAD processing, including sensitivity,
276/// audio chunking, and speech segment filtering. Adjust these fields to control
277/// how strictly speech is detected and how short segments are filtered out.
278///
279/// # Fields
280///
281/// - `enabled`: Whether local VAD is enabled
282/// - `sensitivity`: Speech detection sensitivity (0.0-1.0). Lower values are stricter and less likely to classify audio as speech.
283/// - `padding_chunks`: Number of non-speech chunks to include before and after detected speech
284/// - `min_speech_duration_ms`: Minimum duration (ms) for a segment to be considered valid speech
285///
286/// # Examples
287///
288/// ```rust
289/// use subx_cli::config::VadConfig;
290///
291/// let vad = VadConfig::default();
292/// assert!(vad.enabled);
293/// assert_eq!(vad.sensitivity, 0.25);
294/// ```
295#[derive(Debug, Serialize, Deserialize, Clone)]
296pub struct VadConfig {
297 /// Whether to enable local VAD method.
298 pub enabled: bool,
299 /// Speech detection sensitivity (0.0-1.0).
300 ///
301 /// Lower values are stricter: a smaller value means the detector is less likely to classify a chunk as speech.
302 /// For example, 0.25 is more strict than 0.75.
303 pub sensitivity: f32,
304 /// Number of non-speech chunks to pad before and after detected speech.
305 pub padding_chunks: u32,
306 /// Minimum speech duration in milliseconds.
307 ///
308 /// Segments shorter than this value will be discarded as noise or non-speech.
309 pub min_speech_duration_ms: u32,
310}
311
312#[allow(deprecated)]
313impl Default for SyncConfig {
314 fn default() -> Self {
315 Self {
316 default_method: "auto".to_string(),
317 max_offset_seconds: 60.0,
318 vad: VadConfig::default(),
319 correlation_threshold: 0.8,
320 dialogue_detection_threshold: 0.6,
321 min_dialogue_duration_ms: 500,
322 dialogue_merge_gap_ms: 200,
323 enable_dialogue_detection: true,
324 audio_sample_rate: 44100,
325 auto_detect_sample_rate: true,
326 }
327 }
328}
329
330impl Default for VadConfig {
331 fn default() -> Self {
332 Self {
333 enabled: true,
334 sensitivity: 0.25, // Default changed to 0.25, the smaller the value, the stricter the detection
335 padding_chunks: 3,
336 min_speech_duration_ms: 300,
337 }
338 }
339}
340
341/// General configuration settings for the SubX CLI tool.
342///
343/// This struct contains general settings that control the overall behavior
344/// of the application, including backup policies, processing limits, and
345/// user interface preferences.
346///
347/// # Examples
348///
349/// ```rust
350/// use subx_cli::config::GeneralConfig;
351///
352/// let config = GeneralConfig::default();
353/// assert_eq!(config.max_concurrent_jobs, 4);
354/// assert!(!config.backup_enabled);
355/// ```
356#[derive(Debug, Serialize, Deserialize, Clone)]
357pub struct GeneralConfig {
358 /// Enable automatic backup of original files.
359 pub backup_enabled: bool,
360 /// Maximum number of concurrent processing jobs.
361 pub max_concurrent_jobs: usize,
362 /// Task timeout in seconds.
363 pub task_timeout_seconds: u64,
364 /// Workspace directory for CLI commands (override current working directory).
365 pub workspace: std::path::PathBuf,
366 /// Enable progress bar display.
367 pub enable_progress_bar: bool,
368 /// Worker idle timeout in seconds.
369 pub worker_idle_timeout_seconds: u64,
370}
371
372impl Default for GeneralConfig {
373 fn default() -> Self {
374 Self {
375 backup_enabled: false,
376 max_concurrent_jobs: 4,
377 task_timeout_seconds: 300,
378 // Default workspace is current directory
379 workspace: std::path::PathBuf::from("."),
380 enable_progress_bar: true,
381 worker_idle_timeout_seconds: 60,
382 }
383 }
384}
385
386/// Parallel processing configuration.
387///
388/// Controls how parallel processing is performed, including worker
389/// management, task distribution, and overflow handling strategies.
390///
391/// # Examples
392///
393/// ```rust
394/// use subx_cli::config::{ParallelConfig, OverflowStrategy};
395///
396/// let parallel = ParallelConfig::default();
397/// assert!(parallel.max_workers > 0);
398/// assert_eq!(parallel.overflow_strategy, OverflowStrategy::Block);
399/// ```
400#[derive(Debug, Serialize, Deserialize, Clone)]
401pub struct ParallelConfig {
402 /// Maximum number of worker threads.
403 pub max_workers: usize,
404 /// Strategy for handling task overflow when queues are full.
405 ///
406 /// Determines the behavior when the task queue reaches capacity.
407 /// - [`OverflowStrategy::Block`] - Block until space is available
408 /// - [`OverflowStrategy::Drop`] - Drop new tasks when full
409 /// - [`OverflowStrategy::Expand`] - Dynamically expand queue size
410 pub overflow_strategy: OverflowStrategy,
411 /// Task queue size.
412 pub task_queue_size: usize,
413 /// Enable task priorities.
414 pub enable_task_priorities: bool,
415 /// Auto-balance workers.
416 pub auto_balance_workers: bool,
417}
418
419impl Default for ParallelConfig {
420 fn default() -> Self {
421 Self {
422 max_workers: num_cpus::get(),
423 overflow_strategy: OverflowStrategy::Block,
424 task_queue_size: 1000,
425 enable_task_priorities: false,
426 auto_balance_workers: true,
427 }
428 }
429}
430
431/// Strategy for handling overflow when all workers are busy.
432///
433/// This enum defines different strategies for handling situations where
434/// all worker threads are occupied and new tasks arrive.
435///
436/// # Examples
437///
438/// ```rust
439/// use subx_cli::config::OverflowStrategy;
440///
441/// let strategy = OverflowStrategy::Block;
442/// assert_eq!(strategy, OverflowStrategy::Block);
443///
444/// // Comparison and serialization
445/// let strategies = vec![
446/// OverflowStrategy::Block,
447/// OverflowStrategy::Drop,
448/// OverflowStrategy::Expand,
449/// ];
450/// assert_eq!(strategies.len(), 3);
451/// ```
452#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
453pub enum OverflowStrategy {
454 /// Block until a worker becomes available.
455 ///
456 /// This is the safest option as it ensures all tasks are processed,
457 /// but may cause the application to become unresponsive.
458 Block,
459 /// Drop new tasks when all workers are busy.
460 ///
461 /// Use this when task loss is acceptable and responsiveness is critical.
462 Drop,
463 /// Create additional temporary workers.
464 ///
465 /// This can help with load spikes but may consume excessive resources.
466 Expand,
467 /// Drop oldest tasks in queue.
468 ///
469 /// Prioritizes recent tasks over older ones in the queue.
470 DropOldest,
471 /// Reject new tasks.
472 ///
473 /// Similar to Drop but may provide error feedback to the caller.
474 Reject,
475}
476
477// ============================================================================
478// Configuration Tests
479// ============================================================================
480
481#[cfg(test)]
482mod config_tests {
483 use super::*;
484
485 #[test]
486 fn test_default_config_creation() {
487 let config = Config::default();
488 assert_eq!(config.ai.provider, "openai");
489 assert_eq!(config.ai.model, "gpt-4.1-mini");
490 assert_eq!(config.formats.default_output, "srt");
491 assert!(!config.general.backup_enabled);
492 assert_eq!(config.general.max_concurrent_jobs, 4);
493 }
494
495 #[test]
496 fn test_ai_config_defaults() {
497 let ai_config = AIConfig::default();
498 assert_eq!(ai_config.provider, "openai");
499 assert_eq!(ai_config.model, "gpt-4.1-mini");
500 assert_eq!(ai_config.temperature, 0.3);
501 assert_eq!(ai_config.max_sample_length, 3000);
502 assert_eq!(ai_config.max_tokens, 10000);
503 }
504
505 #[test]
506 fn test_ai_config_max_tokens_configuration() {
507 let mut ai_config = AIConfig::default();
508 ai_config.max_tokens = 5000;
509 assert_eq!(ai_config.max_tokens, 5000);
510
511 // Test with different value
512 ai_config.max_tokens = 20000;
513 assert_eq!(ai_config.max_tokens, 20000);
514 }
515
516 #[test]
517 fn test_new_sync_config_defaults() {
518 let sync = SyncConfig::default();
519 assert_eq!(sync.default_method, "auto");
520 assert_eq!(sync.max_offset_seconds, 60.0);
521 assert!(sync.vad.enabled);
522 }
523
524 #[test]
525 fn test_sync_config_validation() {
526 let mut sync = SyncConfig::default();
527
528 // Valid configuration should pass validation
529 assert!(sync.validate().is_ok());
530
531 // Invalid default_method
532 sync.default_method = "invalid".to_string();
533 assert!(sync.validate().is_err());
534
535 // Reset and test other invalid values
536 sync = SyncConfig::default();
537 sync.max_offset_seconds = -1.0;
538 assert!(sync.validate().is_err());
539 }
540
541 #[test]
542 fn test_vad_config_validation() {
543 let mut vad = VadConfig::default();
544
545 // Valid configuration
546 assert!(vad.validate().is_ok());
547
548 // Invalid sensitivity
549 vad.sensitivity = 1.5;
550 assert!(vad.validate().is_err());
551 }
552
553 #[test]
554 fn test_config_serialization_with_new_sync() {
555 let config = Config::default();
556 let toml_str = toml::to_string(&config).unwrap();
557
558 // Ensure new configuration structure exists in serialized output
559 assert!(toml_str.contains("[sync]"));
560 assert!(toml_str.contains("[sync.vad]"));
561 assert!(toml_str.contains("default_method"));
562 // Whisper-related fields removed, should not appear in serialized output
563 assert!(!toml_str.contains("[sync.whisper]"));
564 assert!(!toml_str.contains("analysis_window_seconds"));
565 }
566}
567
568// ============================================================================
569// Public API Re-exports
570// ============================================================================
571
572// Re-export the configuration service system
573pub use builder::TestConfigBuilder;
574pub use environment::{EnvironmentProvider, SystemEnvironmentProvider, TestEnvironmentProvider};
575pub use service::{ConfigService, ProductionConfigService};
576pub use test_service::TestConfigService;
577
578// Re-export commonly used validation functions
579pub use field_validator::validate_field;
580pub use validator::validate_config;