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