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
84crate::impl_meta_methods!(Integer);
85
86impl Validator for Integer {
87 fn meta(&self) -> &Meta {
88 &self.meta
89 }
90
91 fn meta_mut(&mut self) -> &mut Meta {
92 &mut self.meta
93 }
94
95 fn check(&self, value: &mut Value) -> Result<(), Error> {
96 let coerced = match value {
97 Value::Int(number) => *number,
98 Value::Float(number) => match f64_to_isize(*number) {
99 Some(number) => number,
100 None => {
101 return Err(Error::new(ErrorKind::NotConvertible {
102 target: ValueType::Int,
103 found: ValueType::Float,
104 }));
105 }
106 },
107 Value::String(text) => {
108 if let Ok(number) = text.parse::<isize>() {
109 number
110 } else if let Ok(number) = text.parse::<f64>() {
111 match f64_to_isize(number) {
112 Some(number) => number,
113 None => {
114 return Err(Error::new(ErrorKind::NotConvertible {
115 target: ValueType::Int,
116 found: ValueType::String,
117 }));
118 }
119 }
120 } else {
121 return Err(Error::new(ErrorKind::NotConvertible {
122 target: ValueType::Int,
123 found: ValueType::String,
124 }));
125 }
126 }
127 other => {
128 return Err(Error::new(ErrorKind::Type {
129 expected: ValueType::Int,
130 found: other.type_name(),
131 }));
132 }
133 };
134
135 if let Some(min) = self.min
136 && coerced < min
137 {
138 return Err(Error::new(ErrorKind::BelowMin {
139 value: coerced.to_string(),
140 min: min.to_string(),
141 }));
142 }
143 if let Some(max) = self.max
144 && coerced > max
145 {
146 return Err(Error::new(ErrorKind::AboveMax {
147 value: coerced.to_string(),
148 max: max.to_string(),
149 }));
150 }
151
152 check_sign(self.sign, coerced as f64)?;
153
154 *value = Value::Int(coerced);
155 Ok(())
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn accepts_integer_in_range() {
165 let mut value = Value::Int(50);
166 assert!(Integer::new().range(0, 100).validate(&mut value).is_ok());
167 }
168
169 #[test]
170 fn rejects_out_of_range() {
171 let mut value = Value::Int(200);
172 let error = Integer::new().max(100).validate(&mut value).unwrap_err();
173 assert!(matches!(error.kind, ErrorKind::AboveMax { .. }));
174 }
175
176 #[test]
177 fn coerces_integer_string() {
178 let mut value = Value::String("42".into());
179 Integer::new().validate(&mut value).unwrap();
180 assert_eq!(value, Value::Int(42));
181 }
182
183 #[test]
184 fn coerces_integral_float_string() {
185 let mut value = Value::String("3.0".into());
186 Integer::new().validate(&mut value).unwrap();
187 assert_eq!(value, Value::Int(3));
188 }
189
190 #[test]
191 fn coerces_integral_float() {
192 let mut value = Value::Float(7.0);
193 Integer::new().validate(&mut value).unwrap();
194 assert_eq!(value, Value::Int(7));
195 }
196
197 #[test]
198 fn rejects_fractional_float() {
199 let mut value = Value::Float(3.5);
200 let error = Integer::new().validate(&mut value).unwrap_err();
201 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
202 }
203
204 #[test]
205 fn rejects_non_numeric_string() {
206 let mut value = Value::String("abc".into());
207 let error = Integer::new().validate(&mut value).unwrap_err();
208 assert!(matches!(error.kind, ErrorKind::NotConvertible { .. }));
209 }
210}