reinhardt_forms/fields/
float_field.rs1use crate::field::{FieldError, FieldResult, FormField, Widget};
2
3#[derive(Debug, Clone)]
5pub struct FloatField {
6 pub name: String,
8 pub label: Option<String>,
10 pub required: bool,
12 pub help_text: Option<String>,
14 pub widget: Widget,
16 pub initial: Option<serde_json::Value>,
18 pub max_value: Option<f64>,
20 pub min_value: Option<f64>,
22}
23
24impl FloatField {
25 pub fn new(name: String) -> Self {
37 Self {
38 name,
39 label: None,
40 required: true,
41 help_text: None,
42 widget: Widget::NumberInput,
43 initial: None,
44 max_value: None,
45 min_value: None,
46 }
47 }
48}
49
50impl FormField for FloatField {
51 fn name(&self) -> &str {
52 &self.name
53 }
54
55 fn label(&self) -> Option<&str> {
56 self.label.as_deref()
57 }
58
59 fn required(&self) -> bool {
60 self.required
61 }
62
63 fn help_text(&self) -> Option<&str> {
64 self.help_text.as_deref()
65 }
66
67 fn widget(&self) -> &Widget {
68 &self.widget
69 }
70
71 fn initial(&self) -> Option<&serde_json::Value> {
72 self.initial.as_ref()
73 }
74
75 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
76 match value {
77 None if self.required => Err(FieldError::required(None)),
78 None => Ok(serde_json::Value::Null),
79 Some(v) => {
80 let num = if let Some(f) = v.as_f64() {
82 f
83 } else if let Some(s) = v.as_str() {
84 let s = s.trim();
86
87 if s.is_empty() {
89 if self.required {
90 return Err(FieldError::required(None));
91 }
92 return Ok(serde_json::Value::Null);
93 }
94
95 s.parse::<f64>()
97 .map_err(|_| FieldError::Invalid("Enter a number".to_string()))?
98 } else if let Some(i) = v.as_i64() {
99 i as f64
101 } else {
102 return Err(FieldError::Invalid("Expected number or string".to_string()));
103 };
104
105 if !num.is_finite() {
107 return Err(FieldError::Invalid("Enter a valid number".to_string()));
108 }
109
110 if let Some(max) = self.max_value
112 && num > max
113 {
114 return Err(FieldError::Validation(format!(
115 "Ensure this value is less than or equal to {}",
116 max
117 )));
118 }
119
120 if let Some(min) = self.min_value
121 && num < min
122 {
123 return Err(FieldError::Validation(format!(
124 "Ensure this value is greater than or equal to {}",
125 min
126 )));
127 }
128
129 Ok(serde_json::json!(num))
130 }
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_floatfield_basic() {
141 let field = FloatField::new("price".to_string());
142
143 assert_eq!(
144 field.clean(Some(&serde_json::json!(3.15))).unwrap(),
145 serde_json::json!(3.15)
146 );
147 assert_eq!(
148 field.clean(Some(&serde_json::json!(42))).unwrap(),
149 serde_json::json!(42.0)
150 );
151 }
152
153 #[test]
154 fn test_floatfield_string_parsing() {
155 let mut field = FloatField::new("value".to_string());
156 field.required = false;
157
158 assert_eq!(
159 field.clean(Some(&serde_json::json!("3.15"))).unwrap(),
160 serde_json::json!(3.15)
161 );
162 assert_eq!(
163 field.clean(Some(&serde_json::json!(" -2.5 "))).unwrap(),
164 serde_json::json!(-2.5)
165 );
166 assert_eq!(
167 field.clean(Some(&serde_json::json!("42"))).unwrap(),
168 serde_json::json!(42.0)
169 );
170 }
171
172 #[test]
173 fn test_floatfield_range() {
174 let mut field = FloatField::new("score".to_string());
175 field.min_value = Some(0.0);
176 field.max_value = Some(100.0);
177
178 assert!(field.clean(Some(&serde_json::json!(50.0))).is_ok());
179 assert!(field.clean(Some(&serde_json::json!(0.0))).is_ok());
180 assert!(field.clean(Some(&serde_json::json!(100.0))).is_ok());
181
182 assert!(matches!(
183 field.clean(Some(&serde_json::json!(-1.0))),
184 Err(FieldError::Validation(_))
185 ));
186 assert!(matches!(
187 field.clean(Some(&serde_json::json!(101.0))),
188 Err(FieldError::Validation(_))
189 ));
190 }
191
192 #[test]
193 fn test_floatfield_invalid() {
194 let field = FloatField::new("value".to_string());
195
196 assert!(matches!(
197 field.clean(Some(&serde_json::json!("abc"))),
198 Err(FieldError::Invalid(_))
199 ));
200 }
201
202 #[test]
203 fn test_floatfield_required() {
204 let field = FloatField::new("value".to_string());
205
206 assert!(field.clean(None).is_err());
208
209 assert!(field.clean(Some(&serde_json::json!(""))).is_err());
211 }
212
213 #[test]
214 fn test_floatfield_not_required() {
215 let mut field = FloatField::new("value".to_string());
216 field.required = false;
217
218 assert_eq!(field.clean(None).unwrap(), serde_json::Value::Null);
220
221 assert_eq!(
223 field.clean(Some(&serde_json::json!(""))).unwrap(),
224 serde_json::Value::Null
225 );
226 }
227
228 #[test]
229 fn test_floatfield_negative_numbers() {
230 let mut field = FloatField::new("value".to_string());
231 field.required = false;
232
233 assert_eq!(
234 field.clean(Some(&serde_json::json!(-3.15))).unwrap(),
235 serde_json::json!(-3.15)
236 );
237 assert_eq!(
238 field.clean(Some(&serde_json::json!("-3.15"))).unwrap(),
239 serde_json::json!(-3.15)
240 );
241 }
242
243 #[test]
244 fn test_floatfield_scientific_notation() {
245 let mut field = FloatField::new("value".to_string());
246 field.required = false;
247
248 assert_eq!(
250 field.clean(Some(&serde_json::json!("1.5e2"))).unwrap(),
251 serde_json::json!(150.0)
252 );
253 assert_eq!(
254 field.clean(Some(&serde_json::json!("1e-3"))).unwrap(),
255 serde_json::json!(0.001)
256 );
257 }
258
259 #[test]
260 fn test_floatfield_infinity() {
261 let field = FloatField::new("value".to_string());
262
263 assert!(
265 field
266 .clean(Some(&serde_json::json!(f64::INFINITY)))
267 .is_err()
268 );
269 assert!(
270 field
271 .clean(Some(&serde_json::json!(f64::NEG_INFINITY)))
272 .is_err()
273 );
274 }
275
276 #[test]
277 fn test_floatfield_nan() {
278 let field = FloatField::new("value".to_string());
279
280 assert!(field.clean(Some(&serde_json::json!(f64::NAN))).is_err());
282 }
283
284 #[test]
285 fn test_floatfield_very_small_numbers() {
286 let mut field = FloatField::new("value".to_string());
287 field.required = false;
288
289 assert_eq!(
291 field.clean(Some(&serde_json::json!(0.000001))).unwrap(),
292 serde_json::json!(0.000001)
293 );
294 assert_eq!(
295 field.clean(Some(&serde_json::json!("0.000001"))).unwrap(),
296 serde_json::json!(0.000001)
297 );
298 }
299
300 #[test]
301 fn test_floatfield_very_large_numbers() {
302 let mut field = FloatField::new("value".to_string());
303 field.required = false;
304
305 assert_eq!(
307 field.clean(Some(&serde_json::json!(1000000000.0))).unwrap(),
308 serde_json::json!(1000000000.0)
309 );
310 }
311
312 #[test]
313 fn test_floatfield_max_value_boundary() {
314 let mut field = FloatField::new("value".to_string());
315 field.max_value = Some(100.0);
316
317 assert!(field.clean(Some(&serde_json::json!(100.0))).is_ok());
319
320 assert!(field.clean(Some(&serde_json::json!(100.001))).is_err());
322 }
323
324 #[test]
325 fn test_floatfield_min_value_boundary() {
326 let mut field = FloatField::new("value".to_string());
327 field.min_value = Some(-100.0);
328
329 assert!(field.clean(Some(&serde_json::json!(-100.0))).is_ok());
331
332 assert!(field.clean(Some(&serde_json::json!(-100.001))).is_err());
334 }
335
336 #[test]
337 fn test_floatfield_zero() {
338 let field = FloatField::new("value".to_string());
339
340 assert_eq!(
341 field.clean(Some(&serde_json::json!(0.0))).unwrap(),
342 serde_json::json!(0.0)
343 );
344 assert_eq!(
345 field.clean(Some(&serde_json::json!("0.0"))).unwrap(),
346 serde_json::json!(0.0)
347 );
348 }
349
350 #[test]
351 fn test_floatfield_widget() {
352 let field = FloatField::new("value".to_string());
353 assert!(matches!(field.widget(), &Widget::NumberInput));
354 }
355}