reinhardt_forms/fields/
datetime_field.rs1use crate::field::{FieldError, FieldResult, FormField, Widget};
2use chrono::NaiveDateTime;
3
4pub struct DateTimeField {
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 DateTimeField {
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 "%Y-%m-%d %H:%M:%S".to_string(),
43 "%Y-%m-%d %H:%M".to_string(),
44 "%Y-%m-%dT%H:%M:%S".to_string(),
45 "%Y-%m-%dT%H:%M".to_string(),
46 "%m/%d/%Y %H:%M:%S".to_string(),
47 "%m/%d/%Y %H:%M".to_string(),
48 "%m/%d/%y %H:%M:%S".to_string(),
49 "%m/%d/%y %H:%M".to_string(),
50 ],
51 }
52 }
53
54 fn parse_datetime(&self, s: &str) -> Result<NaiveDateTime, String> {
55 for fmt in &self.input_formats {
56 if let Ok(dt) = NaiveDateTime::parse_from_str(s, fmt) {
57 return Ok(dt);
58 }
59 }
60 Err("Enter a valid date/time".to_string())
61 }
62}
63
64impl FormField for DateTimeField {
65 fn name(&self) -> &str {
66 &self.name
67 }
68
69 fn label(&self) -> Option<&str> {
70 self.label.as_deref()
71 }
72
73 fn required(&self) -> bool {
74 self.required
75 }
76
77 fn help_text(&self) -> Option<&str> {
78 self.help_text.as_deref()
79 }
80
81 fn widget(&self) -> &Widget {
82 &self.widget
83 }
84
85 fn initial(&self) -> Option<&serde_json::Value> {
86 self.initial.as_ref()
87 }
88
89 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
90 match value {
91 None if self.required => Err(FieldError::required(None)),
92 None => Ok(serde_json::Value::Null),
93 Some(v) => {
94 let s = v
95 .as_str()
96 .ok_or_else(|| FieldError::Invalid("Expected string".to_string()))?;
97
98 let s = s.trim();
99
100 if s.is_empty() {
101 if self.required {
102 return Err(FieldError::required(None));
103 }
104 return Ok(serde_json::Value::Null);
105 }
106
107 let dt = self.parse_datetime(s).map_err(FieldError::Validation)?;
108
109 Ok(serde_json::Value::String(
110 dt.format("%Y-%m-%d %H:%M:%S").to_string(),
111 ))
112 }
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_datetimefield_valid() {
123 let field = DateTimeField::new("created_at".to_string());
124
125 assert_eq!(
126 field
127 .clean(Some(&serde_json::json!("2025-01-15 14:30:00")))
128 .unwrap(),
129 serde_json::json!("2025-01-15 14:30:00")
130 );
131 assert_eq!(
132 field
133 .clean(Some(&serde_json::json!("2025-01-15T14:30:00")))
134 .unwrap(),
135 serde_json::json!("2025-01-15 14:30:00")
136 );
137 assert_eq!(
138 field
139 .clean(Some(&serde_json::json!("01/15/2025 14:30:00")))
140 .unwrap(),
141 serde_json::json!("2025-01-15 14:30:00")
142 );
143 }
144
145 #[test]
146 fn test_datetimefield_invalid() {
147 let field = DateTimeField::new("created_at".to_string());
148
149 assert!(matches!(
150 field.clean(Some(&serde_json::json!("not a datetime"))),
151 Err(FieldError::Validation(_))
152 ));
153 assert!(matches!(
154 field.clean(Some(&serde_json::json!("2025-13-01 14:30:00"))),
155 Err(FieldError::Validation(_))
156 ));
157 }
158
159 #[test]
160 fn test_datetimefield_optional() {
161 let mut field = DateTimeField::new("created_at".to_string());
162 field.required = false;
163
164 assert_eq!(field.clean(None).unwrap(), serde_json::Value::Null);
165 assert_eq!(
166 field.clean(Some(&serde_json::json!(""))).unwrap(),
167 serde_json::Value::Null
168 );
169 }
170
171 #[test]
172 fn test_datetimefield_required() {
173 let field = DateTimeField::new("created_at".to_string());
174
175 assert!(field.clean(None).is_err());
177
178 assert!(field.clean(Some(&serde_json::json!(""))).is_err());
180 }
181
182 #[test]
183 fn test_datetimefield_iso_format_with_seconds() {
184 let field = DateTimeField::new("created_at".to_string());
185
186 let result = field
188 .clean(Some(&serde_json::json!("2025-01-15 14:30:00")))
189 .unwrap();
190 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
191
192 let result = field
194 .clean(Some(&serde_json::json!("2025-01-15T14:30:00")))
195 .unwrap();
196 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
197 }
198
199 #[test]
200 fn test_datetimefield_iso_format_without_seconds() {
201 let field = DateTimeField::new("created_at".to_string());
202
203 let result = field
205 .clean(Some(&serde_json::json!("2025-01-15 14:30")))
206 .unwrap();
207 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
208
209 let result = field
211 .clean(Some(&serde_json::json!("2025-01-15T14:30")))
212 .unwrap();
213 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
214 }
215
216 #[test]
217 fn test_datetimefield_us_format_with_seconds() {
218 let field = DateTimeField::new("created_at".to_string());
219
220 let result = field
222 .clean(Some(&serde_json::json!("01/15/2025 14:30:00")))
223 .unwrap();
224 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
225
226 let result = field
228 .clean(Some(&serde_json::json!("01/15/25 14:30:00")))
229 .unwrap();
230 assert_eq!(result, serde_json::json!("0025-01-15 14:30:00"));
231 }
232
233 #[test]
234 fn test_datetimefield_us_format_without_seconds() {
235 let field = DateTimeField::new("created_at".to_string());
236
237 let result = field
239 .clean(Some(&serde_json::json!("01/15/2025 14:30")))
240 .unwrap();
241 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
242
243 let result = field
245 .clean(Some(&serde_json::json!("01/15/25 14:30")))
246 .unwrap();
247 assert_eq!(result, serde_json::json!("0025-01-15 14:30:00"));
248 }
249
250 #[test]
251 fn test_datetimefield_whitespace_trimming() {
252 let field = DateTimeField::new("created_at".to_string());
253
254 let result = field
255 .clean(Some(&serde_json::json!(" 2025-01-15 14:30:00 ")))
256 .unwrap();
257 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
258 }
259
260 #[test]
261 fn test_datetimefield_invalid_date() {
262 let field = DateTimeField::new("created_at".to_string());
263
264 assert!(matches!(
266 field.clean(Some(&serde_json::json!("2025-13-01 14:30:00"))),
267 Err(FieldError::Validation(_))
268 ));
269
270 assert!(matches!(
272 field.clean(Some(&serde_json::json!("2025-01-32 14:30:00"))),
273 Err(FieldError::Validation(_))
274 ));
275
276 assert!(matches!(
278 field.clean(Some(&serde_json::json!("2025-02-30 14:30:00"))),
279 Err(FieldError::Validation(_))
280 ));
281 }
282
283 #[test]
284 fn test_datetimefield_invalid_time() {
285 let field = DateTimeField::new("created_at".to_string());
286
287 assert!(matches!(
289 field.clean(Some(&serde_json::json!("2025-01-15 25:30:00"))),
290 Err(FieldError::Validation(_))
291 ));
292
293 assert!(matches!(
295 field.clean(Some(&serde_json::json!("2025-01-15 14:61:00"))),
296 Err(FieldError::Validation(_))
297 ));
298
299 assert!(matches!(
301 field.clean(Some(&serde_json::json!("2025-01-15 14:30:61"))),
302 Err(FieldError::Validation(_))
303 ));
304 }
305
306 #[test]
307 fn test_datetimefield_leap_year() {
308 let field = DateTimeField::new("created_at".to_string());
309
310 let result = field
312 .clean(Some(&serde_json::json!("2024-02-29 14:30:00")))
313 .unwrap();
314 assert_eq!(result, serde_json::json!("2024-02-29 14:30:00"));
315
316 assert!(matches!(
318 field.clean(Some(&serde_json::json!("2025-02-29 14:30:00"))),
319 Err(FieldError::Validation(_))
320 ));
321 }
322
323 #[test]
324 fn test_datetimefield_midnight() {
325 let field = DateTimeField::new("created_at".to_string());
326
327 let result = field
328 .clean(Some(&serde_json::json!("2025-01-15 00:00:00")))
329 .unwrap();
330 assert_eq!(result, serde_json::json!("2025-01-15 00:00:00"));
331 }
332
333 #[test]
334 fn test_datetimefield_end_of_day() {
335 let field = DateTimeField::new("created_at".to_string());
336
337 let result = field
338 .clean(Some(&serde_json::json!("2025-01-15 23:59:59")))
339 .unwrap();
340 assert_eq!(result, serde_json::json!("2025-01-15 23:59:59"));
341 }
342
343 #[test]
344 fn test_datetimefield_noon() {
345 let field = DateTimeField::new("created_at".to_string());
346
347 let result = field
348 .clean(Some(&serde_json::json!("2025-01-15 12:00:00")))
349 .unwrap();
350 assert_eq!(result, serde_json::json!("2025-01-15 12:00:00"));
351 }
352
353 #[test]
354 fn test_datetimefield_widget_type() {
355 let field = DateTimeField::new("created_at".to_string());
356 assert!(matches!(field.widget(), &Widget::TextInput));
357 }
358
359 #[test]
360 fn test_datetimefield_custom_formats() {
361 let mut field = DateTimeField::new("created_at".to_string());
362 field.input_formats.push("%d-%m-%Y %H:%M:%S".to_string());
364
365 let result = field
367 .clean(Some(&serde_json::json!("15-01-2025 14:30:00")))
368 .unwrap();
369 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
370 }
371
372 #[test]
373 fn test_datetimefield_format_precedence() {
374 let field = DateTimeField::new("created_at".to_string());
375
376 let result = field
379 .clean(Some(&serde_json::json!("2025-01-15 14:30:00")))
380 .unwrap();
381 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
382 }
383}