1use crate::field::{FieldError, FieldResult, FormField, Widget};
2use chrono::NaiveDateTime;
3
4pub struct MultiValueField {
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 fields: Vec<Box<dyn FormField>>,
20 pub require_all_fields: bool,
22}
23
24impl MultiValueField {
25 pub fn new(name: String, fields: Vec<Box<dyn FormField>>) -> Self {
48 Self {
49 name,
50 label: None,
51 required: true,
52 help_text: None,
53 widget: Widget::TextInput,
54 initial: None,
55 fields,
56 require_all_fields: true,
57 }
58 }
59 pub fn compress(&self, values: Vec<serde_json::Value>) -> FieldResult<serde_json::Value> {
61 Ok(serde_json::Value::Array(values))
63 }
64}
65
66impl FormField for MultiValueField {
67 fn name(&self) -> &str {
68 &self.name
69 }
70
71 fn label(&self) -> Option<&str> {
72 self.label.as_deref()
73 }
74
75 fn required(&self) -> bool {
76 self.required
77 }
78
79 fn help_text(&self) -> Option<&str> {
80 self.help_text.as_deref()
81 }
82
83 fn widget(&self) -> &Widget {
84 &self.widget
85 }
86
87 fn initial(&self) -> Option<&serde_json::Value> {
88 self.initial.as_ref()
89 }
90
91 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
92 match value {
93 None if self.required => Err(FieldError::required(None)),
94 None => Ok(serde_json::Value::Null),
95 Some(v) => {
96 let values = v
97 .as_array()
98 .ok_or_else(|| FieldError::invalid(None, "Expected array"))?;
99
100 if values.is_empty() && self.required {
101 return Err(FieldError::required(None));
102 }
103
104 if values.len() != self.fields.len() {
105 return Err(FieldError::invalid(
106 None,
107 &format!("Expected {} values", self.fields.len()),
108 ));
109 }
110
111 let mut cleaned_values = Vec::new();
112 for (idx, field) in self.fields.iter().enumerate() {
113 let field_value = values.get(idx);
114
115 match field.clean(field_value) {
116 Ok(cleaned) => {
117 if cleaned.is_null() && self.require_all_fields {
118 return Err(FieldError::validation(
119 None,
120 "All fields are required",
121 ));
122 }
123 cleaned_values.push(cleaned);
124 }
125 Err(e) => return Err(e),
126 }
127 }
128
129 self.compress(cleaned_values)
130 }
131 }
132 }
133}
134
135pub struct SplitDateTimeField {
137 pub name: String,
139 pub label: Option<String>,
141 pub required: bool,
143 pub help_text: Option<String>,
145 pub widget: Widget,
147 pub initial: Option<serde_json::Value>,
149 pub input_date_formats: Vec<String>,
151 pub input_time_formats: Vec<String>,
153}
154
155impl SplitDateTimeField {
156 pub fn new(name: String) -> Self {
158 Self {
159 name,
160 label: None,
161 required: true,
162 help_text: None,
163 widget: Widget::TextInput,
164 initial: None,
165 input_date_formats: vec![
166 "%Y-%m-%d".to_string(),
167 "%m/%d/%Y".to_string(),
168 "%m/%d/%y".to_string(),
169 ],
170 input_time_formats: vec![
171 "%H:%M:%S".to_string(),
172 "%H:%M".to_string(),
173 "%I:%M:%S %p".to_string(),
174 "%I:%M %p".to_string(),
175 ],
176 }
177 }
178
179 fn parse_date(&self, s: &str) -> Result<chrono::NaiveDate, String> {
180 for fmt in &self.input_date_formats {
181 if let Ok(date) = chrono::NaiveDate::parse_from_str(s, fmt) {
182 return Ok(date);
183 }
184 }
185 Err("Enter a valid date".to_string())
186 }
187
188 fn parse_time(&self, s: &str) -> Result<chrono::NaiveTime, String> {
189 for fmt in &self.input_time_formats {
190 if let Ok(time) = chrono::NaiveTime::parse_from_str(s, fmt) {
191 return Ok(time);
192 }
193 }
194 Err("Enter a valid time".to_string())
195 }
196}
197
198impl FormField for SplitDateTimeField {
199 fn name(&self) -> &str {
200 &self.name
201 }
202
203 fn label(&self) -> Option<&str> {
204 self.label.as_deref()
205 }
206
207 fn required(&self) -> bool {
208 self.required
209 }
210
211 fn help_text(&self) -> Option<&str> {
212 self.help_text.as_deref()
213 }
214
215 fn widget(&self) -> &Widget {
216 &self.widget
217 }
218
219 fn initial(&self) -> Option<&serde_json::Value> {
220 self.initial.as_ref()
221 }
222
223 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
224 match value {
225 None if self.required => Err(FieldError::required(None)),
226 None => Ok(serde_json::Value::Null),
227 Some(v) => {
228 let parts = v
230 .as_array()
231 .ok_or_else(|| FieldError::invalid(None, "Expected array of [date, time]"))?;
232
233 if parts.len() != 2 {
234 return Err(FieldError::invalid(None, "Expected [date, time]"));
235 }
236
237 let date_str = parts[0]
238 .as_str()
239 .ok_or_else(|| FieldError::invalid(None, "Date must be a string"))?;
240
241 let time_str = parts[1]
242 .as_str()
243 .ok_or_else(|| FieldError::invalid(None, "Time must be a string"))?;
244
245 if date_str.trim().is_empty() || time_str.trim().is_empty() {
246 if self.required {
247 return Err(FieldError::required(None));
248 }
249 return Ok(serde_json::Value::Null);
250 }
251
252 let date = self
253 .parse_date(date_str.trim())
254 .map_err(|e| FieldError::validation(None, &e))?;
255
256 let time = self
257 .parse_time(time_str.trim())
258 .map_err(|e| FieldError::validation(None, &e))?;
259
260 let datetime = NaiveDateTime::new(date, time);
261
262 Ok(serde_json::Value::String(
263 datetime.format("%Y-%m-%d %H:%M:%S").to_string(),
264 ))
265 }
266 }
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use crate::fields::{CharField, IntegerField};
274
275 #[test]
276 fn test_multi_value_field() {
277 let fields: Vec<Box<dyn FormField>> = vec![
278 Box::new(CharField::new("first".to_string())),
279 Box::new(IntegerField::new("second".to_string())),
280 ];
281
282 let field = MultiValueField::new("combined".to_string(), fields);
283
284 let value = serde_json::json!(["hello", 42]);
285 let result = field.clean(Some(&value));
286 assert!(result.is_ok());
287
288 let wrong_value = serde_json::json!(["hello"]);
290 assert!(matches!(
291 field.clean(Some(&wrong_value)),
292 Err(FieldError::Invalid(_))
293 ));
294 }
295
296 #[test]
297 fn test_split_datetime_field() {
298 let field = SplitDateTimeField::new("when".to_string());
299
300 let value = serde_json::json!(["2025-01-15", "14:30:00"]);
301 let result = field.clean(Some(&value)).unwrap();
302 assert_eq!(result, serde_json::json!("2025-01-15 14:30:00"));
303
304 let value2 = serde_json::json!(["01/15/2025", "02:30 PM"]);
306 let result2 = field.clean(Some(&value2)).unwrap();
307 assert_eq!(result2, serde_json::json!("2025-01-15 14:30:00"));
308 }
309
310 #[test]
311 fn test_split_datetime_field_invalid() {
312 let field = SplitDateTimeField::new("when".to_string());
313
314 let value = serde_json::json!(["not-a-date", "14:30:00"]);
316 assert!(matches!(
317 field.clean(Some(&value)),
318 Err(FieldError::Validation(_))
319 ));
320
321 let value2 = serde_json::json!(["2025-01-15"]);
323 assert!(matches!(
324 field.clean(Some(&value2)),
325 Err(FieldError::Invalid(_))
326 ));
327 }
328}