tanzim_validate/
integer.rs1use crate::error::{Error, ErrorKind};
2use crate::number::{Sign, check_sign};
3use crate::{Meta, Validator};
4use tanzim_value::{Value, ValueType};
5
6fn f64_to_isize(number: f64) -> Option<isize> {
9 if number.fract() != 0.0 {
10 return None;
11 }
12 if number < isize::MIN as f64 || number > isize::MAX as f64 {
13 return None;
14 }
15 Some(number as isize)
16}
17
18#[derive(Debug, Clone, Default)]
25pub struct Integer {
26 meta: Meta,
27 min: Option<isize>,
28 max: Option<isize>,
29 sign: Option<Sign>,
30}
31
32impl Integer {
33 pub fn with_meta(mut self, meta: Meta) -> Self {
35 self.meta = meta;
36 self
37 }
38
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 pub fn min(mut self, min: isize) -> Self {
44 self.min = Some(min);
45 self
46 }
47
48 pub fn max(mut self, max: isize) -> Self {
49 self.max = Some(max);
50 self
51 }
52
53 pub fn range(mut self, start: isize, end: isize) -> Self {
54 self.min = Some(start);
55 self.max = Some(end);
56 self
57 }
58
59 pub fn positive(mut self) -> Self {
61 self.sign = Some(Sign::Positive);
62 self
63 }
64
65 pub fn non_negative(mut self) -> Self {
67 self.sign = Some(Sign::NonNegative);
68 self
69 }
70
71 pub fn negative(mut self) -> Self {
73 self.sign = Some(Sign::Negative);
74 self
75 }
76
77 pub fn non_positive(mut self) -> Self {
79 self.sign = Some(Sign::NonPositive);
80 self
81 }
82}
83
84impl Validator for Integer {
85 fn meta(&self) -> &Meta {
86 &self.meta
87 }
88
89 fn meta_mut(&mut self) -> &mut Meta {
90 &mut self.meta
91 }
92
93 fn check(&self, value: &mut Value) -> Result<(), Error> {
94 let coerced = match value {
95 Value::Int(number) => *number,
96 Value::Float(number) => match f64_to_isize(*number) {
97 Some(number) => number,
98 None => {
99 return Err(Error::new(ErrorKind::NotConvertible {
100 target: ValueType::Int,
101 found: ValueType::Float,
102 }));
103 }
104 },
105 Value::String(text) => {
106 if let Ok(number) = text.parse::<isize>() {
107 number
108 } else if let Ok(number) = text.parse::<f64>() {
109 match f64_to_isize(number) {
110 Some(number) => number,
111 None => {
112 return Err(Error::new(ErrorKind::NotConvertible {
113 target: ValueType::Int,
114 found: ValueType::String,
115 }));
116 }
117 }
118 } else {
119 return Err(Error::new(ErrorKind::NotConvertible {
120 target: ValueType::Int,
121 found: ValueType::String,
122 }));
123 }
124 }
125 other => {
126 return Err(Error::new(ErrorKind::Type {
127 expected: ValueType::Int,
128 found: other.type_name(),
129 }));
130 }
131 };
132
133 if let Some(min) = self.min
134 && coerced < min
135 {
136 return Err(Error::new(ErrorKind::BelowMin {
137 value: coerced.to_string(),
138 min: min.to_string(),
139 }));
140 }
141 if let Some(max) = self.max
142 && coerced > max
143 {
144 return Err(Error::new(ErrorKind::AboveMax {
145 value: coerced.to_string(),
146 max: max.to_string(),
147 }));
148 }
149
150 check_sign(self.sign, coerced as f64)?;
151
152 *value = Value::Int(coerced);
153 Ok(())
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn accepts_integer_in_range() {
163 let mut value = Value::Int(50);
164 assert!(Integer::new().range(0, 100).validate(&mut value).is_ok());
165 }
166
167 #[test]
168 fn rejects_out_of_range() {
169 let mut value = Value::Int(200);
170 let error = Integer::new().max(100).validate(&mut value).unwrap_err();
171 assert!(matches!(error.kind, ErrorKind::AboveMax { .. }));
172 }
173
174 #[test]
175 fn coerces_integer_string() {
176 let mut value = Value::String("42".into());
177 Integer::new().validate(&mut value).unwrap();
178 assert_eq!(value, Value::Int(42));
179 }
180
181 #[test]
182 fn coerces_integral_float_string() {
183 let mut value = Value::String("3.0".into());
184 Integer::new().validate(&mut value).unwrap();
185 assert_eq!(value, Value::Int(3));
186 }
187
188 #[test]
189 fn coerces_integral_float() {
190 let mut value = Value::Float(7.0);
191 Integer::new().validate(&mut value).unwrap();
192 assert_eq!(value, Value::Int(7));
193 }
194
195 #[test]
196 fn rejects_fractional_float() {
197 let mut value = Value::Float(3.5);
198 let error = Integer::new().validate(&mut value).unwrap_err();
199 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
200 }
201
202 #[test]
203 fn rejects_non_numeric_string() {
204 let mut value = Value::String("abc".into());
205 let error = Integer::new().validate(&mut value).unwrap_err();
206 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
207 }
208}