1use crate::form::Form;
2use std::collections::HashMap;
3
4pub struct FormSet {
6 forms: Vec<Form>,
7 prefix: String,
8 can_delete: bool,
9 can_order: bool,
10 extra: usize,
11 max_num: Option<usize>,
12 min_num: usize,
13 errors: Vec<String>,
14}
15
16impl FormSet {
17 pub fn new(prefix: String) -> Self {
29 let prefix = prefix
32 .strip_suffix('-')
33 .map_or(prefix.clone(), |s| s.to_owned());
34 Self {
35 forms: vec![],
36 prefix,
37 can_delete: false,
38 can_order: false,
39 extra: 1,
40 max_num: Some(1000),
41 min_num: 0,
42 errors: vec![],
43 }
44 }
45
46 pub fn prefix(&self) -> &str {
48 &self.prefix
49 }
50
51 pub fn can_delete(&self) -> bool {
53 self.can_delete
54 }
55 pub fn with_extra(mut self, extra: usize) -> Self {
57 self.extra = extra;
58 self
59 }
60 pub fn with_can_delete(mut self, can_delete: bool) -> Self {
62 self.can_delete = can_delete;
63 self
64 }
65 pub fn with_can_order(mut self, can_order: bool) -> Self {
67 self.can_order = can_order;
68 self
69 }
70 pub fn with_max_num(mut self, max_num: Option<usize>) -> Self {
72 self.max_num = max_num;
73 self
74 }
75 pub fn with_min_num(mut self, min_num: usize) -> Self {
77 self.min_num = min_num;
78 self
79 }
80 pub fn add_form(&mut self, form: Form) -> Result<(), String> {
103 if let Some(max) = self.max_num
104 && self.forms.len() >= max
105 {
106 return Err(format!(
107 "Cannot add form: maximum number of forms ({}) reached",
108 max
109 ));
110 }
111 self.forms.push(form);
112 Ok(())
113 }
114 pub fn forms(&self) -> &[Form] {
116 &self.forms
117 }
118 pub fn forms_mut(&mut self) -> &mut Vec<Form> {
120 &mut self.forms
121 }
122 pub fn form_count(&self) -> usize {
124 self.forms.len()
125 }
126 pub fn total_form_count(&self) -> usize {
128 self.forms.len() + self.extra
129 }
130 pub fn is_valid(&mut self) -> bool {
143 self.errors.clear();
144
145 let mut all_valid = true;
147 for form in &mut self.forms {
148 if !form.is_valid() {
149 all_valid = false;
150 }
151 }
152
153 if self.forms.len() < self.min_num {
155 self.errors
156 .push(format!("Please submit at least {} forms", self.min_num));
157 all_valid = false;
158 }
159
160 if let Some(max) = self.max_num
162 && self.forms.len() > max
163 {
164 self.errors
165 .push(format!("Please submit no more than {} forms", max));
166 all_valid = false;
167 }
168
169 all_valid && self.errors.is_empty()
170 }
171 pub fn errors(&self) -> &[String] {
173 &self.errors
174 }
175 pub fn cleaned_data(&self) -> Vec<&HashMap<String, serde_json::Value>> {
177 self.forms.iter().map(|f| f.cleaned_data()).collect()
178 }
179 pub fn management_form_data(&self) -> HashMap<String, String> {
191 let mut data = HashMap::new();
192 data.insert(
193 format!("{}-TOTAL_FORMS", self.prefix),
194 self.total_form_count().to_string(),
195 );
196 data.insert(
197 format!("{}-INITIAL_FORMS", self.prefix),
198 self.forms.len().to_string(),
199 );
200 data.insert(
201 format!("{}-MIN_NUM_FORMS", self.prefix),
202 self.min_num.to_string(),
203 );
204 if let Some(max) = self.max_num {
205 data.insert(format!("{}-MAX_NUM_FORMS", self.prefix), max.to_string());
206 }
207 data
208 }
209 pub fn process_data(&mut self, data: &HashMap<String, HashMap<String, serde_json::Value>>) {
230 self.forms.clear();
231
232 let mut keys: Vec<&String> = data.keys().collect();
234 keys.sort();
235
236 let prefix_with_delimiter = format!("{}-", self.prefix);
240 for key in keys {
241 if key.starts_with(&prefix_with_delimiter) {
242 if let Some(max) = self.max_num
244 && self.forms.len() >= max
245 {
246 break;
247 }
248 if let Some(form_data) = data.get(key) {
249 let mut form = Form::new();
250 form.bind(form_data.clone());
251 self.forms.push(form);
252 }
253 }
254 }
255 }
256}
257
258impl Default for FormSet {
259 fn default() -> Self {
260 Self::new("form".to_string())
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267 use crate::fields::CharField;
268 use rstest::rstest;
269
270 #[test]
271 fn test_formset_basic() {
272 let mut formset = FormSet::new("person".to_string());
273
274 let mut form1 = Form::new();
275 form1.add_field(Box::new(CharField::new("name".to_string())));
276
277 let mut form2 = Form::new();
278 form2.add_field(Box::new(CharField::new("name".to_string())));
279
280 formset.add_form(form1).unwrap();
281 formset.add_form(form2).unwrap();
282
283 assert_eq!(formset.form_count(), 2);
284 }
285
286 #[test]
287 fn test_formset_min_num_validation() {
288 let mut formset = FormSet::new("person".to_string()).with_min_num(2);
289
290 let mut form1 = Form::new();
291 form1.add_field(Box::new(CharField::new("name".to_string())));
292 formset.add_form(form1).unwrap();
293
294 assert!(!formset.is_valid());
295 assert!(!formset.errors().is_empty());
296 }
297
298 #[test]
299 fn test_formset_max_num_enforced_on_add() {
300 let mut formset = FormSet::new("person".to_string()).with_max_num(Some(2));
301
302 let mut form1 = Form::new();
303 form1.add_field(Box::new(CharField::new("name".to_string())));
304 assert!(formset.add_form(form1).is_ok());
305
306 let mut form2 = Form::new();
307 form2.add_field(Box::new(CharField::new("name".to_string())));
308 assert!(formset.add_form(form2).is_ok());
309
310 let mut form3 = Form::new();
312 form3.add_field(Box::new(CharField::new("name".to_string())));
313 assert!(formset.add_form(form3).is_err());
314
315 assert_eq!(formset.form_count(), 2);
316 }
317
318 #[rstest]
319 fn test_process_data_basic_two_forms() {
320 let mut formset = FormSet::new("form".to_string());
322 let mut data = HashMap::new();
323
324 let mut form0_data = HashMap::new();
325 form0_data.insert("name".to_string(), serde_json::json!("Alice"));
326 data.insert("form-0".to_string(), form0_data);
327
328 let mut form1_data = HashMap::new();
329 form1_data.insert("name".to_string(), serde_json::json!("Bob"));
330 data.insert("form-1".to_string(), form1_data);
331
332 formset.process_data(&data);
334
335 assert_eq!(formset.form_count(), 2);
337 }
338
339 #[rstest]
340 fn test_process_data_deterministic_ordering() {
341 let mut formset = FormSet::new("form".to_string());
343 let mut data = HashMap::new();
344
345 let mut form2_data = HashMap::new();
347 form2_data.insert("name".to_string(), serde_json::json!("Charlie"));
348 data.insert("form-2".to_string(), form2_data);
349
350 let mut form0_data = HashMap::new();
351 form0_data.insert("name".to_string(), serde_json::json!("Alice"));
352 data.insert("form-0".to_string(), form0_data);
353
354 let mut form1_data = HashMap::new();
355 form1_data.insert("name".to_string(), serde_json::json!("Bob"));
356 data.insert("form-1".to_string(), form1_data);
357
358 formset.process_data(&data);
360
361 assert_eq!(formset.form_count(), 3);
363 let cleaned: Vec<_> = formset.cleaned_data();
364 assert_eq!(cleaned[0].get("name"), Some(&serde_json::json!("Alice")));
365 assert_eq!(cleaned[1].get("name"), Some(&serde_json::json!("Bob")));
366 assert_eq!(cleaned[2].get("name"), Some(&serde_json::json!("Charlie")));
367 }
368
369 #[rstest]
370 fn test_process_data_max_num_constraint() {
371 let mut formset = FormSet::new("form".to_string()).with_max_num(Some(2));
373 let mut data = HashMap::new();
374
375 for i in 0..5 {
376 let mut form_data = HashMap::new();
377 form_data.insert("name".to_string(), serde_json::json!(format!("User{}", i)));
378 data.insert(format!("form-{}", i), form_data);
379 }
380
381 formset.process_data(&data);
383
384 assert_eq!(formset.form_count(), 2);
386 }
387
388 #[rstest]
389 fn test_process_data_prefix_mismatch_keys_ignored() {
390 let mut formset = FormSet::new("person".to_string());
392 let mut data = HashMap::new();
393
394 let mut matching = HashMap::new();
395 matching.insert("name".to_string(), serde_json::json!("Alice"));
396 data.insert("person-0".to_string(), matching);
397
398 let mut mismatched = HashMap::new();
399 mismatched.insert("name".to_string(), serde_json::json!("Bob"));
400 data.insert("form-0".to_string(), mismatched);
401
402 formset.process_data(&data);
404
405 assert_eq!(formset.form_count(), 1);
407 let cleaned = formset.cleaned_data();
408 assert_eq!(cleaned[0].get("name"), Some(&serde_json::json!("Alice")));
409 }
410
411 #[rstest]
412 fn test_process_data_prefix_collision_prevented() {
413 let mut formset = FormSet::new("item".to_string());
415 let mut data = HashMap::new();
416
417 let mut matching = HashMap::new();
418 matching.insert("name".to_string(), serde_json::json!("Apple"));
419 data.insert("item-0".to_string(), matching);
420
421 let mut colliding = HashMap::new();
422 colliding.insert("name".to_string(), serde_json::json!("Banana"));
423 data.insert("item_extra-0".to_string(), colliding);
424
425 formset.process_data(&data);
427
428 assert_eq!(formset.form_count(), 1);
430 let cleaned = formset.cleaned_data();
431 assert_eq!(cleaned[0].get("name"), Some(&serde_json::json!("Apple")));
432 }
433
434 #[rstest]
435 fn test_process_data_similar_prefix_no_collision() {
436 let mut formset = FormSet::new("form".to_string());
438 let mut data = HashMap::new();
439
440 let mut matching = HashMap::new();
441 matching.insert("name".to_string(), serde_json::json!("Valid"));
442 data.insert("form-0".to_string(), matching);
443
444 let mut similar1 = HashMap::new();
445 similar1.insert("name".to_string(), serde_json::json!("Invalid1"));
446 data.insert("formset-0".to_string(), similar1);
447
448 let mut similar2 = HashMap::new();
449 similar2.insert("name".to_string(), serde_json::json!("Invalid2"));
450 data.insert("form2-0".to_string(), similar2);
451
452 formset.process_data(&data);
454
455 assert_eq!(formset.form_count(), 1);
457 let cleaned = formset.cleaned_data();
458 assert_eq!(cleaned[0].get("name"), Some(&serde_json::json!("Valid")));
459 }
460
461 #[test]
462 fn test_forms_formset_management_data() {
463 let formset = FormSet::new("person".to_string())
464 .with_extra(3)
465 .with_min_num(1)
466 .with_max_num(Some(10));
467
468 let mgmt_data = formset.management_form_data();
469
470 assert_eq!(mgmt_data.get("person-TOTAL_FORMS"), Some(&"3".to_string()));
471 assert_eq!(
472 mgmt_data.get("person-INITIAL_FORMS"),
473 Some(&"0".to_string())
474 );
475 assert_eq!(
476 mgmt_data.get("person-MIN_NUM_FORMS"),
477 Some(&"1".to_string())
478 );
479 assert_eq!(
480 mgmt_data.get("person-MAX_NUM_FORMS"),
481 Some(&"10".to_string())
482 );
483 }
484}