use crate::error::{Result, ValidationError};
pub fn validate_range(
value: f64,
arg_name: &str,
min: Option<f64>,
max: Option<f64>,
) -> Result<()> {
if value.is_nan() {
return Err(ValidationError::OutOfRange {
arg_name: arg_name.to_string(),
value,
min: min.unwrap_or(f64::NEG_INFINITY),
max: max.unwrap_or(f64::INFINITY),
suggestion: Some(
"Provide at least one of 'min' or 'max' in the validation rule.".to_string(),
),
}
.into());
}
if let Some(min_val) = min {
if value < min_val {
return Err(ValidationError::OutOfRange {
arg_name: arg_name.to_string(),
value,
min: min_val,
max: max.unwrap_or(f64::INFINITY),
suggestion: Some(format!(
"Value must be greater than or equal to {}.",
min_val
)),
}
.into());
}
}
if let Some(max_val) = max {
if value > max_val {
return Err(ValidationError::OutOfRange {
arg_name: arg_name.to_string(),
value,
min: min.unwrap_or(f64::NEG_INFINITY),
max: max_val,
suggestion: Some(format!("Value must be less than or equal to {}.", max_val)),
}
.into());
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_range_within_bounds() {
assert!(validate_range(50.0, "percentage", Some(0.0), Some(100.0)).is_ok());
assert!(validate_range(0.0, "percentage", Some(0.0), Some(100.0)).is_ok());
assert!(validate_range(100.0, "percentage", Some(0.0), Some(100.0)).is_ok());
}
#[test]
fn test_validate_range_below_minimum() {
let result = validate_range(-10.0, "percentage", Some(0.0), Some(100.0));
assert!(result.is_err());
match result.unwrap_err() {
crate::error::DynamicCliError::Validation(ValidationError::OutOfRange {
arg_name,
value,
min,
max,
..
}) => {
assert_eq!(arg_name, "percentage");
assert_eq!(value, -10.0);
assert_eq!(min, 0.0);
assert_eq!(max, 100.0);
}
other => panic!("Expected OutOfRange error, got {:?}", other),
}
}
#[test]
fn test_validate_range_above_maximum() {
let result = validate_range(150.0, "percentage", Some(0.0), Some(100.0));
assert!(result.is_err());
match result.unwrap_err() {
crate::error::DynamicCliError::Validation(ValidationError::OutOfRange {
arg_name,
value,
min,
max,
..
}) => {
assert_eq!(arg_name, "percentage");
assert_eq!(value, 150.0);
assert_eq!(min, 0.0);
assert_eq!(max, 100.0);
}
other => panic!("Expected OutOfRange error, got {:?}", other),
}
}
#[test]
fn test_validate_range_min_only_valid() {
assert!(validate_range(5.0, "count", Some(0.0), None).is_ok());
assert!(validate_range(0.0, "count", Some(0.0), None).is_ok());
assert!(validate_range(1_000_000.0, "count", Some(0.0), None).is_ok());
}
#[test]
fn test_validate_range_min_only_invalid() {
let result = validate_range(-5.0, "count", Some(0.0), None);
assert!(result.is_err());
match result.unwrap_err() {
crate::error::DynamicCliError::Validation(ValidationError::OutOfRange {
arg_name,
value,
min,
max,
..
}) => {
assert_eq!(arg_name, "count");
assert_eq!(value, -5.0);
assert_eq!(min, 0.0);
assert!(max.is_infinite() && max.is_sign_positive()); }
other => panic!("Expected OutOfRange error, got {:?}", other),
}
}
#[test]
fn test_validate_range_max_only_valid() {
assert!(validate_range(0.5, "probability", None, Some(1.0)).is_ok());
assert!(validate_range(1.0, "probability", None, Some(1.0)).is_ok());
assert!(validate_range(-1_000_000.0, "temperature", None, Some(100.0)).is_ok());
}
#[test]
fn test_validate_range_max_only_invalid() {
let result = validate_range(1.5, "probability", None, Some(1.0));
assert!(result.is_err());
match result.unwrap_err() {
crate::error::DynamicCliError::Validation(ValidationError::OutOfRange {
arg_name,
value,
min,
max,
..
}) => {
assert_eq!(arg_name, "probability");
assert_eq!(value, 1.5);
assert!(min.is_infinite() && min.is_sign_negative()); assert_eq!(max, 1.0);
}
other => panic!("Expected OutOfRange error, got {:?}", other),
}
}
#[test]
fn test_validate_range_no_bounds() {
assert!(validate_range(0.0, "value", None, None).is_ok());
assert!(validate_range(-1000.0, "value", None, None).is_ok());
assert!(validate_range(1000.0, "value", None, None).is_ok());
assert!(validate_range(f64::INFINITY, "value", None, None).is_ok());
assert!(validate_range(f64::NEG_INFINITY, "value", None, None).is_ok());
}
#[test]
fn test_validate_range_nan() {
let result = validate_range(f64::NAN, "value", Some(0.0), Some(100.0));
assert!(result.is_err());
let result = validate_range(f64::NAN, "value", None, None);
assert!(result.is_err());
}
#[test]
fn test_validate_range_infinity_positive() {
assert!(validate_range(f64::INFINITY, "value", Some(0.0), Some(100.0)).is_err());
assert!(validate_range(f64::INFINITY, "value", Some(0.0), None).is_ok());
assert!(validate_range(f64::INFINITY, "value", None, None).is_ok());
}
#[test]
fn test_validate_range_infinity_negative() {
assert!(validate_range(f64::NEG_INFINITY, "value", Some(0.0), Some(100.0)).is_err());
assert!(validate_range(f64::NEG_INFINITY, "value", None, Some(100.0)).is_ok());
assert!(validate_range(f64::NEG_INFINITY, "value", None, None).is_ok());
}
#[test]
fn test_validate_range_exact_boundaries() {
assert!(validate_range(0.0, "x", Some(0.0), Some(1.0)).is_ok());
assert!(validate_range(1.0, "x", Some(0.0), Some(1.0)).is_ok());
assert!(validate_range(0.0, "x", Some(0.0), Some(0.0)).is_ok()); }
#[test]
fn test_validate_range_tiny_differences() {
assert!(validate_range(0.0000001, "x", Some(0.0), Some(1.0)).is_ok());
assert!(validate_range(0.9999999, "x", Some(0.0), Some(1.0)).is_ok());
assert!(validate_range(-0.0000001, "x", Some(0.0), Some(1.0)).is_err());
assert!(validate_range(1.0000001, "x", Some(0.0), Some(1.0)).is_err());
}
#[test]
fn test_validate_range_negative_values() {
assert!(validate_range(-50.0, "temperature", Some(-100.0), Some(0.0)).is_ok());
assert!(validate_range(-100.0, "temperature", Some(-100.0), Some(0.0)).is_ok());
assert!(validate_range(0.0, "temperature", Some(-100.0), Some(0.0)).is_ok());
assert!(validate_range(-150.0, "temperature", Some(-100.0), Some(0.0)).is_err());
assert!(validate_range(10.0, "temperature", Some(-100.0), Some(0.0)).is_err());
}
#[test]
fn test_validate_range_large_values() {
let very_large = 1e308;
assert!(validate_range(very_large, "value", Some(0.0), None).is_ok());
assert!(validate_range(very_large, "value", Some(0.0), Some(1e307)).is_err());
}
#[test]
fn test_validate_range_small_values() {
let very_small = 1e-308;
assert!(validate_range(very_small, "value", Some(0.0), Some(1.0)).is_ok());
assert!(validate_range(very_small, "value", Some(1e-307), Some(1.0)).is_err());
}
#[test]
fn test_validate_range_zero() {
assert!(validate_range(0.0, "x", Some(0.0), Some(1.0)).is_ok());
assert!(validate_range(0.0, "x", Some(-1.0), Some(0.0)).is_ok());
assert!(validate_range(0.0, "x", Some(-1.0), Some(1.0)).is_ok());
assert!(validate_range(0.0, "x", Some(1.0), Some(2.0)).is_err());
assert!(validate_range(0.0, "x", Some(-2.0), Some(-1.0)).is_err());
}
#[test]
fn test_validate_range_realistic_scenarios() {
assert!(validate_range(50.0, "percentage", Some(0.0), Some(100.0)).is_ok());
assert!(validate_range(-1.0, "percentage", Some(0.0), Some(100.0)).is_err());
assert!(validate_range(101.0, "percentage", Some(0.0), Some(100.0)).is_err());
assert!(validate_range(0.5, "probability", Some(0.0), Some(1.0)).is_ok());
assert!(validate_range(1.5, "probability", Some(0.0), Some(1.0)).is_err());
assert!(validate_range(-100.0, "temperature", Some(-273.15), None).is_ok());
assert!(validate_range(-300.0, "temperature", Some(-273.15), None).is_err());
assert!(validate_range(25.0, "age", Some(0.0), Some(150.0)).is_ok());
assert!(validate_range(200.0, "age", Some(0.0), Some(150.0)).is_err());
}
}