rusdantic_core/rules/
range.rs1use crate::error::{PathSegment, ValidationError, ValidationErrors};
7use std::fmt::Display;
8
9pub fn validate_range<T: PartialOrd + Display + Copy>(
22 value: &T,
23 min: Option<T>,
24 max: Option<T>,
25 path: &[PathSegment],
26 errors: &mut ValidationErrors,
27) {
28 if value.partial_cmp(value).is_none() {
31 errors.add(
32 ValidationError::new("range_nan", "value is NaN (not a number)")
33 .with_path(path.to_vec()),
34 );
35 return;
36 }
37
38 if let Some(min_val) = min {
39 if *value < min_val {
40 errors.add(
41 ValidationError::new(
42 "range_min",
43 format!("must be at least {}", min_val),
44 )
45 .with_path(path.to_vec())
46 .with_param("min", serde_json::Value::String(min_val.to_string()))
47 .with_param("actual", serde_json::Value::String(value.to_string())),
48 );
49 }
50 }
51
52 if let Some(max_val) = max {
53 if *value > max_val {
54 errors.add(
55 ValidationError::new(
56 "range_max",
57 format!("must be at most {}", max_val),
58 )
59 .with_path(path.to_vec())
60 .with_param("max", serde_json::Value::String(max_val.to_string()))
61 .with_param("actual", serde_json::Value::String(value.to_string())),
62 );
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 fn path(name: &str) -> Vec<PathSegment> {
72 vec![PathSegment::Field(name.to_string())]
73 }
74
75 #[test]
76 fn test_u8_range_valid() {
77 let mut errors = ValidationErrors::new();
78 validate_range(&25u8, Some(18u8), Some(120u8), &path("age"), &mut errors);
79 assert!(errors.is_empty());
80 }
81
82 #[test]
83 fn test_u8_range_below_min() {
84 let mut errors = ValidationErrors::new();
85 validate_range(&16u8, Some(18u8), None, &path("age"), &mut errors);
86 assert_eq!(errors.len(), 1);
87 assert_eq!(errors.errors()[0].code, "range_min");
88 }
89
90 #[test]
91 fn test_u8_range_above_max() {
92 let mut errors = ValidationErrors::new();
93 validate_range(&200u8, None, Some(150u8), &path("age"), &mut errors);
94 assert_eq!(errors.len(), 1);
95 assert_eq!(errors.errors()[0].code, "range_max");
96 }
97
98 #[test]
99 fn test_i32_range() {
100 let mut errors = ValidationErrors::new();
101 validate_range(&-5i32, Some(-10i32), Some(10i32), &path("temp"), &mut errors);
102 assert!(errors.is_empty());
103 }
104
105 #[test]
106 fn test_i64_range_negative() {
107 let mut errors = ValidationErrors::new();
108 validate_range(&-20i64, Some(-10i64), None, &path("offset"), &mut errors);
109 assert_eq!(errors.len(), 1);
110 }
111
112 #[test]
113 fn test_f64_range() {
114 let mut errors = ValidationErrors::new();
115 validate_range(&3.15f64, Some(0.0f64), Some(10.0f64), &path("ratio"), &mut errors);
116 assert!(errors.is_empty());
117 }
118
119 #[test]
120 fn test_f64_range_below() {
121 let mut errors = ValidationErrors::new();
122 validate_range(&-0.1f64, Some(0.0f64), None, &path("ratio"), &mut errors);
123 assert_eq!(errors.len(), 1);
124 }
125
126 #[test]
127 fn test_boundary_values() {
128 let mut errors = ValidationErrors::new();
129 validate_range(&18u8, Some(18u8), Some(120u8), &path("age"), &mut errors);
131 assert!(errors.is_empty());
132
133 validate_range(&120u8, Some(18u8), Some(120u8), &path("age"), &mut errors);
135 assert!(errors.is_empty());
136 }
137
138 #[test]
139 fn test_no_bounds() {
140 let mut errors = ValidationErrors::new();
141 validate_range::<i32>(&999, None, None, &path("f"), &mut errors);
142 assert!(errors.is_empty());
143 }
144
145 #[test]
146 fn test_usize_range() {
147 let mut errors = ValidationErrors::new();
148 validate_range(&5usize, Some(1usize), Some(100usize), &path("count"), &mut errors);
149 assert!(errors.is_empty());
150 }
151}