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