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