1use crate::error::{SubXError, SubXResult};
16use std::path::Path;
17use url::Url;
18
19pub fn validate_enum(value: &str, allowed: &[&str]) -> SubXResult<()> {
21 if allowed.contains(&value) {
22 Ok(())
23 } else {
24 Err(SubXError::config(format!(
25 "Invalid value '{}'. Allowed values: {}",
26 value,
27 allowed.join(", ")
28 )))
29 }
30}
31
32pub fn validate_float_range(value: &str, min: f32, max: f32) -> SubXResult<f32> {
34 let parsed = value
35 .parse::<f32>()
36 .map_err(|_| SubXError::config(format!("Invalid float value: {}", value)))?;
37 if parsed < min || parsed > max {
38 return Err(SubXError::config(format!(
39 "Value {} is out of range [{}, {}]",
40 parsed, min, max
41 )));
42 }
43 Ok(parsed)
44}
45
46pub fn validate_uint_range(value: &str, min: u32, max: u32) -> SubXResult<u32> {
48 let parsed = value
49 .parse::<u32>()
50 .map_err(|_| SubXError::config(format!("Invalid integer value: {}", value)))?;
51 if parsed < min || parsed > max {
52 return Err(SubXError::config(format!(
53 "Value {} is out of range [{}, {}]",
54 parsed, min, max
55 )));
56 }
57 Ok(parsed)
58}
59
60pub fn validate_u64_range(value: &str, min: u64, max: u64) -> SubXResult<u64> {
62 let parsed = value
63 .parse::<u64>()
64 .map_err(|_| SubXError::config(format!("Invalid u64 value: {}", value)))?;
65 if parsed < min || parsed > max {
66 return Err(SubXError::config(format!(
67 "Value {} is out of range [{}, {}]",
68 parsed, min, max
69 )));
70 }
71 Ok(parsed)
72}
73
74pub fn validate_usize_range(value: &str, min: usize, max: usize) -> SubXResult<usize> {
76 let parsed = value
77 .parse::<usize>()
78 .map_err(|_| SubXError::config(format!("Invalid usize value: {}", value)))?;
79 if parsed < min || parsed > max {
80 return Err(SubXError::config(format!(
81 "Value {} is out of range [{}, {}]",
82 parsed, min, max
83 )));
84 }
85 Ok(parsed)
86}
87
88pub fn validate_api_key(value: &str) -> SubXResult<()> {
90 if value.is_empty() {
91 return Err(SubXError::config("API key cannot be empty".to_string()));
92 }
93 if value.len() < 10 {
94 return Err(SubXError::config("API key is too short".to_string()));
95 }
96 Ok(())
97}
98
99pub fn validate_url(value: &str) -> SubXResult<()> {
101 if !value.starts_with("http://") && !value.starts_with("https://") {
102 return Err(SubXError::config(format!(
103 "Invalid URL format: {}. Must start with http:// or https://",
104 value
105 )));
106 }
107 Ok(())
108}
109
110pub fn parse_bool(value: &str) -> SubXResult<bool> {
112 match value.to_lowercase().as_str() {
113 "true" | "1" | "yes" | "on" | "enabled" => Ok(true),
114 "false" | "0" | "no" | "off" | "disabled" => Ok(false),
115 _ => Err(SubXError::config(format!(
116 "Invalid boolean value: {}",
117 value
118 ))),
119 }
120}
121
122pub fn validate_url_format(value: &str) -> SubXResult<()> {
130 if value.trim().is_empty() {
131 return Ok(()); }
133
134 Url::parse(value).map_err(|_| SubXError::config(format!("Invalid URL format: {}", value)))?;
135 Ok(())
136}
137
138pub fn validate_positive_number<T>(value: T) -> SubXResult<()>
146where
147 T: PartialOrd + Default + std::fmt::Display + Copy,
148{
149 if value <= T::default() {
150 return Err(SubXError::config(format!(
151 "Value must be positive, got: {}",
152 value
153 )));
154 }
155 Ok(())
156}
157
158pub fn validate_range<T>(value: T, min: T, max: T) -> SubXResult<()>
168where
169 T: PartialOrd + std::fmt::Display + Copy,
170{
171 if value < min || value > max {
172 return Err(SubXError::config(format!(
173 "Value {} is outside allowed range [{}, {}]",
174 value, min, max
175 )));
176 }
177 Ok(())
178}
179
180pub fn validate_non_empty_string(value: &str, field_name: &str) -> SubXResult<()> {
189 if value.trim().is_empty() {
190 return Err(SubXError::config(format!("{} cannot be empty", field_name)));
191 }
192 Ok(())
193}
194
195pub fn validate_file_path(value: &str, must_exist: bool) -> SubXResult<()> {
204 if value.trim().is_empty() {
205 return Err(SubXError::config("File path cannot be empty"));
206 }
207
208 let path = Path::new(value);
209 if must_exist && !path.exists() {
210 return Err(SubXError::config(format!("Path does not exist: {}", value)));
211 }
212
213 Ok(())
214}
215
216pub fn validate_temperature(temperature: f32) -> SubXResult<()> {
224 validate_range(temperature, 0.0, 2.0)
225 .map_err(|_| SubXError::config("AI temperature must be between 0.0 and 2.0"))
226}
227
228pub fn validate_ai_model(model: &str) -> SubXResult<()> {
236 validate_non_empty_string(model, "AI model")?;
237
238 if model.len() > 100 {
240 return Err(SubXError::config(
241 "AI model name is too long (max 100 characters)",
242 ));
243 }
244
245 Ok(())
246}
247
248pub fn validate_power_of_two(value: usize) -> SubXResult<()> {
256 if value == 0 || !value.is_power_of_two() {
257 return Err(SubXError::config(format!(
258 "Value {} must be a power of two",
259 value
260 )));
261 }
262 Ok(())
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_validate_url_format() {
271 assert!(validate_url_format("https://api.openai.com").is_ok());
272 assert!(validate_url_format("").is_ok()); assert!(validate_url_format("invalid-url").is_err());
274 assert!(validate_url_format("ftp://example.com").is_ok()); }
276
277 #[test]
278 fn test_validate_positive_number() {
279 assert!(validate_positive_number(1.0).is_ok());
280 assert!(validate_positive_number(0.0).is_err());
281 assert!(validate_positive_number(-1.0).is_err());
282 assert!(validate_positive_number(0.1).is_ok());
283 }
284
285 #[test]
286 fn test_validate_range() {
287 assert!(validate_range(1.5, 0.0, 2.0).is_ok());
288 assert!(validate_range(0.0, 0.0, 2.0).is_ok()); assert!(validate_range(2.0, 0.0, 2.0).is_ok());
290 assert!(validate_range(-0.1, 0.0, 2.0).is_err());
291 assert!(validate_range(2.1, 0.0, 2.0).is_err());
292 }
293
294 #[test]
295 fn test_validate_non_empty_string() {
296 assert!(validate_non_empty_string("test", "field").is_ok());
297 assert!(validate_non_empty_string("", "field").is_err());
298 assert!(validate_non_empty_string(" ", "field").is_err()); assert!(validate_non_empty_string(" test ", "field").is_ok());
300 }
301
302 #[test]
303 fn test_validate_temperature() {
304 assert!(validate_temperature(0.8).is_ok());
305 assert!(validate_temperature(0.0).is_ok());
306 assert!(validate_temperature(2.0).is_ok());
307 assert!(validate_temperature(-0.1).is_err());
308 assert!(validate_temperature(2.1).is_err());
309 }
310
311 #[test]
312 fn test_validate_ai_model() {
313 assert!(validate_ai_model("gpt-4").is_ok());
314 assert!(validate_ai_model("").is_err());
315 assert!(validate_ai_model(&"a".repeat(101)).is_err()); assert!(validate_ai_model(&"a".repeat(100)).is_ok()); }
318
319 #[test]
320 fn test_validate_power_of_two() {
321 assert!(validate_power_of_two(1).is_ok());
322 assert!(validate_power_of_two(2).is_ok());
323 assert!(validate_power_of_two(4).is_ok());
324 assert!(validate_power_of_two(256).is_ok());
325 assert!(validate_power_of_two(1024).is_ok());
326 assert!(validate_power_of_two(0).is_err());
327 assert!(validate_power_of_two(3).is_err());
328 assert!(validate_power_of_two(5).is_err());
329 }
330
331 #[test]
332 fn test_validate_enum() {
333 let allowed = &["openai", "anthropic"];
334 assert!(validate_enum("openai", allowed).is_ok());
335 assert!(validate_enum("anthropic", allowed).is_ok());
336 assert!(validate_enum("invalid", allowed).is_err());
337 }
338
339 #[test]
340 fn test_validate_float_range() {
341 assert!(validate_float_range("1.5", 0.0, 2.0).is_ok());
342 assert!(validate_float_range("0.0", 0.0, 2.0).is_ok());
343 assert!(validate_float_range("2.0", 0.0, 2.0).is_ok());
344 assert!(validate_float_range("-0.1", 0.0, 2.0).is_err());
345 assert!(validate_float_range("2.1", 0.0, 2.0).is_err());
346 assert!(validate_float_range("invalid", 0.0, 2.0).is_err());
347 }
348
349 #[test]
350 fn test_parse_bool() {
351 assert_eq!(parse_bool("true").unwrap(), true);
352 assert_eq!(parse_bool("false").unwrap(), false);
353 assert_eq!(parse_bool("1").unwrap(), true);
354 assert_eq!(parse_bool("0").unwrap(), false);
355 assert_eq!(parse_bool("yes").unwrap(), true);
356 assert_eq!(parse_bool("no").unwrap(), false);
357 assert_eq!(parse_bool("enabled").unwrap(), true);
358 assert_eq!(parse_bool("disabled").unwrap(), false);
359 assert!(parse_bool("invalid").is_err());
360 }
361}