reinhardt_forms/fields/
time_field.rs1use crate::field::{FieldError, FieldResult, FormField, Widget};
2use chrono::NaiveTime;
3
4pub struct TimeField {
6 pub name: String,
7 pub label: Option<String>,
8 pub required: bool,
9 pub help_text: Option<String>,
10 pub widget: Widget,
11 pub initial: Option<serde_json::Value>,
12 pub input_formats: Vec<String>,
13}
14
15impl TimeField {
16 pub fn new(name: String) -> Self {
27 Self {
28 name,
29 label: None,
30 required: true,
31 help_text: None,
32 widget: Widget::TextInput,
33 initial: None,
34 input_formats: vec![
35 "%H:%M:%S".to_string(),
36 "%H:%M".to_string(),
37 "%I:%M:%S %p".to_string(),
38 "%I:%M %p".to_string(),
39 ],
40 }
41 }
42
43 fn parse_time(&self, s: &str) -> Result<NaiveTime, String> {
44 for fmt in &self.input_formats {
45 if let Ok(time) = NaiveTime::parse_from_str(s, fmt) {
46 return Ok(time);
47 }
48 }
49 Err("Enter a valid time".to_string())
50 }
51}
52
53impl FormField for TimeField {
54 fn name(&self) -> &str {
55 &self.name
56 }
57
58 fn label(&self) -> Option<&str> {
59 self.label.as_deref()
60 }
61
62 fn required(&self) -> bool {
63 self.required
64 }
65
66 fn help_text(&self) -> Option<&str> {
67 self.help_text.as_deref()
68 }
69
70 fn widget(&self) -> &Widget {
71 &self.widget
72 }
73
74 fn initial(&self) -> Option<&serde_json::Value> {
75 self.initial.as_ref()
76 }
77
78 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
79 match value {
80 None if self.required => Err(FieldError::required(None)),
81 None => Ok(serde_json::Value::Null),
82 Some(v) => {
83 let s = v
84 .as_str()
85 .ok_or_else(|| FieldError::Invalid("Expected string".to_string()))?;
86
87 let s = s.trim();
88
89 if s.is_empty() {
90 if self.required {
91 return Err(FieldError::required(None));
92 }
93 return Ok(serde_json::Value::Null);
94 }
95
96 let time = self.parse_time(s).map_err(FieldError::Validation)?;
97
98 Ok(serde_json::Value::String(
99 time.format("%H:%M:%S").to_string(),
100 ))
101 }
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn test_timefield_valid() {
112 let field = TimeField::new("start_time".to_string());
113
114 assert_eq!(
115 field.clean(Some(&serde_json::json!("14:30:00"))).unwrap(),
116 serde_json::json!("14:30:00")
117 );
118 assert_eq!(
119 field.clean(Some(&serde_json::json!("14:30"))).unwrap(),
120 serde_json::json!("14:30:00")
121 );
122 assert_eq!(
123 field
124 .clean(Some(&serde_json::json!("02:30:00 PM")))
125 .unwrap(),
126 serde_json::json!("14:30:00")
127 );
128 }
129
130 #[test]
131 fn test_timefield_invalid() {
132 let field = TimeField::new("start_time".to_string());
133
134 assert!(matches!(
135 field.clean(Some(&serde_json::json!("not a time"))),
136 Err(FieldError::Validation(_))
137 ));
138 assert!(matches!(
139 field.clean(Some(&serde_json::json!("25:00:00"))),
140 Err(FieldError::Validation(_))
141 ));
142 }
143
144 #[test]
145 fn test_timefield_optional() {
146 let mut field = TimeField::new("start_time".to_string());
147 field.required = false;
148
149 assert_eq!(field.clean(None).unwrap(), serde_json::Value::Null);
150 assert_eq!(
151 field.clean(Some(&serde_json::json!(""))).unwrap(),
152 serde_json::Value::Null
153 );
154 }
155
156 #[test]
157 fn test_timefield_required() {
158 let field = TimeField::new("start_time".to_string());
159
160 assert!(field.clean(None).is_err());
162
163 assert!(field.clean(Some(&serde_json::json!(""))).is_err());
165 }
166
167 #[test]
168 fn test_timefield_24hour_format_with_seconds() {
169 let field = TimeField::new("start_time".to_string());
170
171 let result = field.clean(Some(&serde_json::json!("14:30:00"))).unwrap();
172 assert_eq!(result, serde_json::json!("14:30:00"));
173
174 let result = field.clean(Some(&serde_json::json!("09:15:30"))).unwrap();
175 assert_eq!(result, serde_json::json!("09:15:30"));
176 }
177
178 #[test]
179 fn test_timefield_24hour_format_without_seconds() {
180 let field = TimeField::new("start_time".to_string());
181
182 let result = field.clean(Some(&serde_json::json!("14:30"))).unwrap();
184 assert_eq!(result, serde_json::json!("14:30:00"));
185
186 let result = field.clean(Some(&serde_json::json!("09:15"))).unwrap();
187 assert_eq!(result, serde_json::json!("09:15:00"));
188 }
189
190 #[test]
191 fn test_timefield_12hour_format_pm() {
192 let field = TimeField::new("start_time".to_string());
193
194 let result = field
196 .clean(Some(&serde_json::json!("02:30:00 PM")))
197 .unwrap();
198 assert_eq!(result, serde_json::json!("14:30:00"));
199
200 let result = field.clean(Some(&serde_json::json!("02:30 PM"))).unwrap();
201 assert_eq!(result, serde_json::json!("14:30:00"));
202
203 let result = field
204 .clean(Some(&serde_json::json!("11:59:59 PM")))
205 .unwrap();
206 assert_eq!(result, serde_json::json!("23:59:59"));
207 }
208
209 #[test]
210 fn test_timefield_12hour_format_am() {
211 let field = TimeField::new("start_time".to_string());
212
213 let result = field
215 .clean(Some(&serde_json::json!("09:30:00 AM")))
216 .unwrap();
217 assert_eq!(result, serde_json::json!("09:30:00"));
218
219 let result = field.clean(Some(&serde_json::json!("09:30 AM"))).unwrap();
220 assert_eq!(result, serde_json::json!("09:30:00"));
221
222 let result = field
223 .clean(Some(&serde_json::json!("11:59:59 AM")))
224 .unwrap();
225 assert_eq!(result, serde_json::json!("11:59:59"));
226 }
227
228 #[test]
229 fn test_timefield_midnight() {
230 let field = TimeField::new("start_time".to_string());
231
232 let result = field.clean(Some(&serde_json::json!("00:00:00"))).unwrap();
234 assert_eq!(result, serde_json::json!("00:00:00"));
235
236 let result = field.clean(Some(&serde_json::json!("12:00 AM"))).unwrap();
238 assert_eq!(result, serde_json::json!("00:00:00"));
239 }
240
241 #[test]
242 fn test_timefield_noon() {
243 let field = TimeField::new("start_time".to_string());
244
245 let result = field.clean(Some(&serde_json::json!("12:00:00"))).unwrap();
247 assert_eq!(result, serde_json::json!("12:00:00"));
248
249 let result = field.clean(Some(&serde_json::json!("12:00 PM"))).unwrap();
251 assert_eq!(result, serde_json::json!("12:00:00"));
252 }
253
254 #[test]
255 fn test_timefield_end_of_day() {
256 let field = TimeField::new("start_time".to_string());
257
258 let result = field.clean(Some(&serde_json::json!("23:59:59"))).unwrap();
259 assert_eq!(result, serde_json::json!("23:59:59"));
260 }
261
262 #[test]
263 fn test_timefield_whitespace_trimming() {
264 let field = TimeField::new("start_time".to_string());
265
266 let result = field
267 .clean(Some(&serde_json::json!(" 14:30:00 ")))
268 .unwrap();
269 assert_eq!(result, serde_json::json!("14:30:00"));
270 }
271
272 #[test]
273 fn test_timefield_invalid_hour() {
274 let field = TimeField::new("start_time".to_string());
275
276 assert!(matches!(
278 field.clean(Some(&serde_json::json!("25:00:00"))),
279 Err(FieldError::Validation(_))
280 ));
281
282 assert!(matches!(
284 field.clean(Some(&serde_json::json!("24:00:00"))),
285 Err(FieldError::Validation(_))
286 ));
287 }
288
289 #[test]
290 fn test_timefield_invalid_minute() {
291 let field = TimeField::new("start_time".to_string());
292
293 assert!(matches!(
295 field.clean(Some(&serde_json::json!("14:60:00"))),
296 Err(FieldError::Validation(_))
297 ));
298
299 assert!(matches!(
301 field.clean(Some(&serde_json::json!("14:99:00"))),
302 Err(FieldError::Validation(_))
303 ));
304 }
305
306 #[test]
307 fn test_timefield_invalid_second() {
308 let field = TimeField::new("start_time".to_string());
309
310 assert!(matches!(
312 field.clean(Some(&serde_json::json!("14:30:99"))),
313 Err(FieldError::Validation(_))
314 ));
315
316 }
319
320 #[test]
321 fn test_timefield_invalid_format() {
322 let field = TimeField::new("start_time".to_string());
323
324 assert!(matches!(
326 field.clean(Some(&serde_json::json!("1430"))),
327 Err(FieldError::Validation(_))
328 ));
329
330 assert!(matches!(
332 field.clean(Some(&serde_json::json!("not a time"))),
333 Err(FieldError::Validation(_))
334 ));
335 }
336
337 #[test]
338 fn test_timefield_widget_type() {
339 let field = TimeField::new("start_time".to_string());
340 assert!(matches!(field.widget(), &Widget::TextInput));
341 }
342
343 #[test]
344 fn test_timefield_custom_format() {
345 let mut field = TimeField::new("start_time".to_string());
346 field.input_formats.clear();
348 field.input_formats.push("%H.%M.%S".to_string());
349
350 let result = field.clean(Some(&serde_json::json!("14.30.00"))).unwrap();
352 assert_eq!(result, serde_json::json!("14:30:00"));
353 }
354
355 #[test]
356 fn test_timefield_format_precedence() {
357 let field = TimeField::new("start_time".to_string());
358
359 let result = field.clean(Some(&serde_json::json!("14:30:00"))).unwrap();
362 assert_eq!(result, serde_json::json!("14:30:00"));
363 }
364}