1use super::validation::*;
14use crate::{Result, error::SubXError};
15
16pub fn validate_field(key: &str, value: &str) -> Result<()> {
28 match key {
29 "ai.provider" => {
31 validate_non_empty_string(value, "AI provider")?;
32 validate_enum(value, &["openai", "anthropic", "local"])?;
33 }
34 "ai.model" => validate_ai_model(value)?,
35 "ai.api_key" => {
36 if !value.is_empty() {
37 validate_api_key(value)?;
38 }
39 }
40 "ai.base_url" => validate_url_format(value)?,
41 "ai.temperature" => {
42 let temp: f32 = value
43 .parse()
44 .map_err(|_| SubXError::config("Temperature must be a number"))?;
45 validate_temperature(temp)?;
46 }
47 "ai.max_tokens" => {
48 let tokens: u32 = value
49 .parse()
50 .map_err(|_| SubXError::config("Max tokens must be a positive integer"))?;
51 validate_positive_number(tokens as f64)?;
52 }
53 "ai.max_sample_length" => {
54 let length: usize = value
55 .parse()
56 .map_err(|_| SubXError::config("Max sample length must be a positive integer"))?;
57 validate_range(length, 100, 10000)?;
58 }
59 "ai.retry_attempts" => {
60 let attempts: u32 = value
61 .parse()
62 .map_err(|_| SubXError::config("Retry attempts must be a positive integer"))?;
63 validate_range(attempts, 1, 10)?;
64 }
65 "ai.retry_delay_ms" => {
66 let delay: u64 = value
67 .parse()
68 .map_err(|_| SubXError::config("Retry delay must be a positive integer"))?;
69 validate_range(delay, 100, 30000)?;
70 }
71 "ai.request_timeout_seconds" => {
72 let timeout: u64 = value
73 .parse()
74 .map_err(|_| SubXError::config("Request timeout must be a positive integer"))?;
75 validate_range(timeout, 10, 600)?;
76 }
77
78 "sync.default_method" => {
80 validate_enum(value, &["auto", "vad", "manual"])?;
81 }
82 "sync.max_offset_seconds" => {
83 let offset: f32 = value
84 .parse()
85 .map_err(|_| SubXError::config("Max offset must be a number"))?;
86 validate_range(offset, 0.1, 3600.0)?;
87 }
88 "sync.vad.enabled" => {
89 parse_bool(value)?;
90 }
91 "sync.vad.sensitivity" => {
92 let sensitivity: f32 = value
93 .parse()
94 .map_err(|_| SubXError::config("VAD sensitivity must be a number"))?;
95 validate_range(sensitivity, 0.0, 1.0)?;
96 }
97 "sync.vad.padding_chunks" => {
98 let chunks: u32 = value
99 .parse()
100 .map_err(|_| SubXError::config("Padding chunks must be a non-negative integer"))?;
101 validate_range(chunks, 0, 10)?;
102 }
103 "sync.vad.min_speech_duration_ms" => {
104 let _duration: u32 = value.parse().map_err(|_| {
105 SubXError::config("Min speech duration must be a non-negative integer")
106 })?;
107 }
109
110 "formats.default_output" => {
112 validate_enum(value, &["srt", "ass", "vtt", "webvtt"])?;
113 }
114 "formats.preserve_styling" => {
115 parse_bool(value)?;
116 }
117 "formats.default_encoding" => {
118 validate_enum(value, &["utf-8", "gbk", "big5", "shift_jis"])?;
119 }
120 "formats.encoding_detection_confidence" => {
121 let confidence: f32 = value
122 .parse()
123 .map_err(|_| SubXError::config("Encoding detection confidence must be a number"))?;
124 validate_range(confidence, 0.0, 1.0)?;
125 }
126
127 "general.backup_enabled" => {
129 parse_bool(value)?;
130 }
131 "general.max_concurrent_jobs" => {
132 let jobs: usize = value
133 .parse()
134 .map_err(|_| SubXError::config("Max concurrent jobs must be a positive integer"))?;
135 validate_range(jobs, 1, 64)?;
136 }
137 "general.task_timeout_seconds" => {
138 let timeout: u64 = value
139 .parse()
140 .map_err(|_| SubXError::config("Task timeout must be a positive integer"))?;
141 validate_range(timeout, 30, 3600)?;
142 }
143 "general.enable_progress_bar" => {
144 parse_bool(value)?;
145 }
146 "general.worker_idle_timeout_seconds" => {
147 let timeout: u64 = value
148 .parse()
149 .map_err(|_| SubXError::config("Worker idle timeout must be a positive integer"))?;
150 validate_range(timeout, 10, 3600)?;
151 }
152
153 "parallel.max_workers" => {
155 let workers: usize = value
156 .parse()
157 .map_err(|_| SubXError::config("Max workers must be a positive integer"))?;
158 validate_range(workers, 1, 64)?;
159 }
160 "parallel.task_queue_size" => {
161 let size: usize = value
162 .parse()
163 .map_err(|_| SubXError::config("Task queue size must be a positive integer"))?;
164 validate_range(size, 100, 10000)?;
165 }
166 "parallel.enable_task_priorities" => {
167 parse_bool(value)?;
168 }
169 "parallel.auto_balance_workers" => {
170 parse_bool(value)?;
171 }
172 "parallel.overflow_strategy" => {
173 validate_enum(value, &["Block", "Drop", "Expand"])?;
174 }
175
176 _ => {
177 return Err(SubXError::config(format!(
178 "Unknown configuration key: {}",
179 key
180 )));
181 }
182 }
183
184 Ok(())
185}
186
187pub fn get_field_description(key: &str) -> &'static str {
189 match key {
190 "ai.provider" => "AI service provider (e.g., 'openai')",
191 "ai.model" => "AI model name (e.g., 'gpt-4.1-mini')",
192 "ai.api_key" => "API key for the AI service",
193 "ai.base_url" => "Custom API endpoint URL (optional)",
194 "ai.temperature" => "AI response randomness (0.0-2.0)",
195 "ai.max_tokens" => "Maximum tokens in AI response",
196 "ai.max_sample_length" => "Maximum sample length for AI processing",
197 "ai.retry_attempts" => "Number of retry attempts for AI requests",
198 "ai.retry_delay_ms" => "Delay between retry attempts in milliseconds",
199 "ai.request_timeout_seconds" => "Request timeout in seconds",
200
201 "sync.default_method" => "Synchronization method ('auto', 'vad', or 'manual')",
202 "sync.max_offset_seconds" => "Maximum allowed time offset in seconds",
203 "sync.vad.enabled" => "Enable voice activity detection",
204 "sync.vad.sensitivity" => "Voice activity detection threshold (0.0-1.0)",
205 "sync.vad.chunk_size" => "VAD processing chunk size (must be power of 2)",
206 "sync.vad.sample_rate" => "Audio sample rate for VAD processing",
207 "sync.vad.padding_chunks" => "Number of padding chunks for VAD",
208 "sync.vad.min_speech_duration_ms" => "Minimum speech duration in milliseconds",
209
210 "formats.default_output" => "Default output format for subtitles",
211 "formats.preserve_styling" => "Preserve subtitle styling information",
212 "formats.default_encoding" => "Default character encoding",
213 "formats.encoding_detection_confidence" => "Confidence threshold for encoding detection",
214
215 "general.backup_enabled" => "Enable automatic backup creation",
216 "general.max_concurrent_jobs" => "Maximum number of concurrent jobs",
217 "general.task_timeout_seconds" => "Task timeout in seconds",
218 "general.enable_progress_bar" => "Enable progress bar display",
219 "general.worker_idle_timeout_seconds" => "Worker idle timeout in seconds",
220
221 "parallel.max_workers" => "Maximum number of worker threads",
222 "parallel.task_queue_size" => "Size of the task queue",
223 "parallel.enable_task_priorities" => "Enable task priority system",
224 "parallel.auto_balance_workers" => "Enable automatic worker load balancing",
225 "parallel.overflow_strategy" => "Strategy for handling queue overflow",
226
227 _ => "Configuration field",
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_validate_ai_fields() {
237 assert!(validate_field("ai.provider", "openai").is_ok());
239 assert!(validate_field("ai.temperature", "0.8").is_ok());
240 assert!(validate_field("ai.max_tokens", "4000").is_ok());
241
242 assert!(validate_field("ai.provider", "invalid").is_err());
244 assert!(validate_field("ai.temperature", "3.0").is_err());
245 assert!(validate_field("ai.max_tokens", "0").is_err());
246 }
247
248 #[test]
249 fn test_validate_sync_fields() {
250 assert!(validate_field("sync.default_method", "vad").is_ok());
252 assert!(validate_field("sync.vad.sensitivity", "0.5").is_ok());
253 assert!(validate_field("sync.vad.padding_chunks", "3").is_ok());
254
255 assert!(validate_field("sync.default_method", "invalid").is_err());
257 assert!(validate_field("sync.vad.sensitivity", "1.5").is_err());
258 assert!(validate_field("sync.vad.padding_chunks", "11").is_err()); }
260
261 #[test]
262 fn test_validate_formats_fields() {
263 assert!(validate_field("formats.default_output", "srt").is_ok());
265 assert!(validate_field("formats.preserve_styling", "true").is_ok());
266
267 assert!(validate_field("formats.default_output", "invalid").is_err());
269 assert!(validate_field("formats.preserve_styling", "maybe").is_err());
270 }
271
272 #[test]
273 fn test_validate_unknown_field() {
274 assert!(validate_field("unknown.field", "value").is_err());
275 }
276
277 #[test]
278 fn test_get_field_description() {
279 assert!(!get_field_description("ai.provider").is_empty());
280 assert!(!get_field_description("sync.vad.sensitivity").is_empty());
281 assert_eq!(
282 get_field_description("unknown.field"),
283 "Configuration field"
284 );
285 }
286}