1use crate::field::{FieldError, FieldResult, FormField, Widget};
2
3#[derive(Debug, Clone)]
5pub struct ChoiceField {
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 choices: Vec<(String, String)>,
20}
21
22impl ChoiceField {
23 pub fn new(name: String, choices: Vec<(String, String)>) -> Self {
35 Self {
36 name,
37 label: None,
38 required: true,
39 help_text: None,
40 widget: Widget::Select {
41 choices: choices.clone(),
42 },
43 initial: None,
44 choices,
45 }
46 }
47}
48
49impl FormField for ChoiceField {
50 fn name(&self) -> &str {
51 &self.name
52 }
53
54 fn label(&self) -> Option<&str> {
55 self.label.as_deref()
56 }
57
58 fn required(&self) -> bool {
59 self.required
60 }
61
62 fn help_text(&self) -> Option<&str> {
63 self.help_text.as_deref()
64 }
65
66 fn widget(&self) -> &Widget {
67 &self.widget
68 }
69
70 fn initial(&self) -> Option<&serde_json::Value> {
71 self.initial.as_ref()
72 }
73
74 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
75 match value {
76 None if self.required => Err(FieldError::required(None)),
77 None => Ok(serde_json::Value::String(String::new())),
78 Some(v) => {
79 let s = v
80 .as_str()
81 .ok_or_else(|| FieldError::Invalid("Expected string".to_string()))?;
82
83 let s = s.trim();
84
85 if s.is_empty() {
86 if self.required {
87 return Err(FieldError::required(None));
88 }
89 return Ok(serde_json::Value::String(String::new()));
90 }
91
92 let valid = self.choices.iter().any(|(value, _)| value == s);
94 if !valid {
95 return Err(FieldError::Validation(format!(
96 "Select a valid choice. '{}' is not one of the available choices",
97 s
98 )));
99 }
100
101 Ok(serde_json::Value::String(s.to_string()))
102 }
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct MultipleChoiceField {
110 pub name: String,
112 pub label: Option<String>,
114 pub required: bool,
116 pub help_text: Option<String>,
118 pub widget: Widget,
120 pub initial: Option<serde_json::Value>,
122 pub choices: Vec<(String, String)>,
124}
125
126impl MultipleChoiceField {
127 pub fn new(name: String, choices: Vec<(String, String)>) -> Self {
129 Self {
130 name,
131 label: None,
132 required: true,
133 help_text: None,
134 widget: Widget::Select {
135 choices: choices.clone(),
136 },
137 initial: None,
138 choices,
139 }
140 }
141}
142
143impl FormField for MultipleChoiceField {
144 fn name(&self) -> &str {
145 &self.name
146 }
147
148 fn label(&self) -> Option<&str> {
149 self.label.as_deref()
150 }
151
152 fn required(&self) -> bool {
153 self.required
154 }
155
156 fn help_text(&self) -> Option<&str> {
157 self.help_text.as_deref()
158 }
159
160 fn widget(&self) -> &Widget {
161 &self.widget
162 }
163
164 fn initial(&self) -> Option<&serde_json::Value> {
165 self.initial.as_ref()
166 }
167
168 fn clean(&self, value: Option<&serde_json::Value>) -> FieldResult<serde_json::Value> {
169 match value {
170 None if self.required => Err(FieldError::required(None)),
171 None => Ok(serde_json::json!([])),
172 Some(v) => {
173 let values: Vec<String> = if let Some(arr) = v.as_array() {
174 arr.iter()
175 .filter_map(|v| v.as_str().map(|s| s.to_string()))
176 .collect()
177 } else if let Some(s) = v.as_str() {
178 vec![s.to_string()]
179 } else {
180 return Err(FieldError::Invalid("Expected array or string".to_string()));
181 };
182
183 if values.is_empty() && self.required {
184 return Err(FieldError::required(None));
185 }
186
187 for val in &values {
189 let valid = self.choices.iter().any(|(choice, _)| choice == val);
190 if !valid {
191 return Err(FieldError::Validation(format!(
192 "Select a valid choice. '{}' is not one of the available choices",
193 val
194 )));
195 }
196 }
197
198 Ok(serde_json::json!(values))
199 }
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_choicefield_valid() {
210 let choices = vec![
211 ("1".to_string(), "One".to_string()),
212 ("2".to_string(), "Two".to_string()),
213 ];
214 let field = ChoiceField::new("number".to_string(), choices);
215
216 assert_eq!(
217 field.clean(Some(&serde_json::json!("1"))).unwrap(),
218 serde_json::json!("1")
219 );
220 }
221
222 #[test]
223 fn test_choicefield_invalid() {
224 let choices = vec![("1".to_string(), "One".to_string())];
225 let field = ChoiceField::new("number".to_string(), choices);
226
227 assert!(matches!(
228 field.clean(Some(&serde_json::json!("3"))),
229 Err(FieldError::Validation(_))
230 ));
231 }
232
233 #[test]
234 fn test_multiplechoicefield() {
235 let choices = vec![
236 ("a".to_string(), "A".to_string()),
237 ("b".to_string(), "B".to_string()),
238 ];
239 let field = MultipleChoiceField::new("letters".to_string(), choices);
240
241 assert_eq!(
242 field.clean(Some(&serde_json::json!(["a", "b"]))).unwrap(),
243 serde_json::json!(["a", "b"])
244 );
245
246 assert!(matches!(
247 field.clean(Some(&serde_json::json!(["a", "c"]))),
248 Err(FieldError::Validation(_))
249 ));
250 }
251
252 #[test]
253 fn test_choicefield_required() {
254 let choices = vec![("1".to_string(), "One".to_string())];
255 let field = ChoiceField::new("number".to_string(), choices);
256
257 assert!(field.clean(None).is_err());
259
260 assert!(field.clean(Some(&serde_json::json!(""))).is_err());
262 }
263
264 #[test]
265 fn test_choicefield_not_required() {
266 let choices = vec![("1".to_string(), "One".to_string())];
267 let mut field = ChoiceField::new("number".to_string(), choices);
268 field.required = false;
269
270 assert_eq!(field.clean(None).unwrap(), serde_json::json!(""));
272
273 assert_eq!(
275 field.clean(Some(&serde_json::json!(""))).unwrap(),
276 serde_json::json!("")
277 );
278 }
279
280 #[test]
281 fn test_choicefield_whitespace_trimming() {
282 let choices = vec![("1".to_string(), "One".to_string())];
283 let field = ChoiceField::new("number".to_string(), choices);
284
285 assert_eq!(
287 field.clean(Some(&serde_json::json!(" 1 "))).unwrap(),
288 serde_json::json!("1")
289 );
290 }
291
292 #[test]
293 fn test_choicefield_multiple_choices() {
294 let choices = vec![
295 ("a".to_string(), "Alpha".to_string()),
296 ("b".to_string(), "Beta".to_string()),
297 ("c".to_string(), "Gamma".to_string()),
298 ];
299 let field = ChoiceField::new("greek".to_string(), choices);
300
301 assert!(field.clean(Some(&serde_json::json!("a"))).is_ok());
303 assert!(field.clean(Some(&serde_json::json!("b"))).is_ok());
304 assert!(field.clean(Some(&serde_json::json!("c"))).is_ok());
305
306 assert!(field.clean(Some(&serde_json::json!("d"))).is_err());
308 }
309
310 #[test]
311 fn test_choicefield_widget_type() {
312 let choices = vec![("1".to_string(), "One".to_string())];
313 let field = ChoiceField::new("number".to_string(), choices.clone());
314
315 match field.widget() {
317 Widget::Select {
318 choices: widget_choices,
319 } => {
320 assert_eq!(widget_choices, &choices);
321 }
322 _ => panic!("Expected Select widget"),
323 }
324 }
325
326 #[test]
327 fn test_choicefield_empty_choices() {
328 let choices: Vec<(String, String)> = vec![];
329 let field = ChoiceField::new("empty".to_string(), choices);
330
331 assert!(matches!(
333 field.clean(Some(&serde_json::json!("anything"))),
334 Err(FieldError::Validation(_))
335 ));
336 }
337
338 #[test]
339 fn test_choicefield_case_sensitive() {
340 let choices = vec![("abc".to_string(), "ABC".to_string())];
341 let field = ChoiceField::new("text".to_string(), choices);
342
343 assert!(field.clean(Some(&serde_json::json!("abc"))).is_ok());
345
346 assert!(matches!(
348 field.clean(Some(&serde_json::json!("ABC"))),
349 Err(FieldError::Validation(_))
350 ));
351 }
352
353 #[test]
354 fn test_multiplechoicefield_required() {
355 let choices = vec![("1".to_string(), "One".to_string())];
356 let field = MultipleChoiceField::new("numbers".to_string(), choices);
357
358 assert!(field.clean(None).is_err());
360
361 assert!(field.clean(Some(&serde_json::json!([]))).is_err());
363 }
364
365 #[test]
366 fn test_multiplechoicefield_not_required() {
367 let choices = vec![("1".to_string(), "One".to_string())];
368 let mut field = MultipleChoiceField::new("numbers".to_string(), choices);
369 field.required = false;
370
371 assert_eq!(field.clean(None).unwrap(), serde_json::json!([]));
373
374 assert_eq!(
376 field.clean(Some(&serde_json::json!([]))).unwrap(),
377 serde_json::json!([])
378 );
379 }
380
381 #[test]
382 fn test_multiplechoicefield_single_value() {
383 let choices = vec![
384 ("a".to_string(), "A".to_string()),
385 ("b".to_string(), "B".to_string()),
386 ];
387 let field = MultipleChoiceField::new("letters".to_string(), choices);
388
389 assert_eq!(
391 field.clean(Some(&serde_json::json!("a"))).unwrap(),
392 serde_json::json!(["a"])
393 );
394
395 assert!(matches!(
397 field.clean(Some(&serde_json::json!("z"))),
398 Err(FieldError::Validation(_))
399 ));
400 }
401
402 #[test]
403 fn test_multiplechoicefield_multiple_values() {
404 let choices = vec![
405 ("1".to_string(), "One".to_string()),
406 ("2".to_string(), "Two".to_string()),
407 ("3".to_string(), "Three".to_string()),
408 ];
409 let field = MultipleChoiceField::new("numbers".to_string(), choices);
410
411 assert_eq!(
413 field.clean(Some(&serde_json::json!(["1", "2"]))).unwrap(),
414 serde_json::json!(["1", "2"])
415 );
416
417 assert_eq!(
418 field
419 .clean(Some(&serde_json::json!(["1", "2", "3"])))
420 .unwrap(),
421 serde_json::json!(["1", "2", "3"])
422 );
423
424 assert!(matches!(
426 field.clean(Some(&serde_json::json!(["1", "2", "4"]))),
427 Err(FieldError::Validation(_))
428 ));
429 }
430
431 #[test]
432 fn test_multiplechoicefield_duplicate_values() {
433 let choices = vec![
434 ("a".to_string(), "A".to_string()),
435 ("b".to_string(), "B".to_string()),
436 ];
437 let field = MultipleChoiceField::new("letters".to_string(), choices);
438
439 let result = field
441 .clean(Some(&serde_json::json!(["a", "a", "b"])))
442 .unwrap();
443 assert_eq!(result, serde_json::json!(["a", "a", "b"]));
444 }
445
446 #[test]
447 fn test_multiplechoicefield_widget_type() {
448 let choices = vec![("1".to_string(), "One".to_string())];
449 let field = MultipleChoiceField::new("numbers".to_string(), choices.clone());
450
451 match field.widget() {
453 Widget::Select {
454 choices: widget_choices,
455 } => {
456 assert_eq!(widget_choices, &choices);
457 }
458 _ => panic!("Expected Select widget"),
459 }
460 }
461}