1use crate::{CharField, EmailField, FloatField, Form, FormError, FormField, IntegerField, Widget};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::marker::PhantomData;
10
11#[derive(Debug, Clone)]
13pub enum FieldType {
14 Char {
16 max_length: Option<usize>,
18 },
19 Text,
21 Integer,
23 Float,
25 Boolean,
27 DateTime,
29 Date,
31 Time,
33 Email,
35 Url,
37 Json,
39}
40
41pub trait FormModel: Send + Sync {
45 fn field_names() -> Vec<String>;
47
48 fn field_type(_name: &str) -> Option<FieldType> {
64 None
65 }
66
67 fn get_field(&self, name: &str) -> Option<Value>;
69
70 fn set_field(&mut self, name: &str, value: Value) -> Result<(), String>;
72
73 fn save(&mut self) -> Result<(), String>;
75
76 fn validate(&self) -> Result<(), Vec<String>> {
91 Ok(())
92 }
93
94 fn to_choice_label(&self) -> String {
110 self.get_field("id")
112 .and_then(|v| v.as_i64().map(|i| i.to_string()))
113 .or_else(|| {
114 self.get_field("id")
115 .and_then(|v| v.as_str().map(|s| s.to_string()))
116 })
117 .unwrap_or_default()
118 }
119
120 fn to_choice_value(&self) -> String {
135 self.get_field("id")
136 .and_then(|v| v.as_i64().map(|i| i.to_string()))
137 .or_else(|| {
138 self.get_field("id")
139 .and_then(|v| v.as_str().map(|s| s.to_string()))
140 })
141 .unwrap_or_default()
142 }
143}
144
145#[derive(Debug, Clone, Default)]
147pub struct ModelFormConfig {
148 pub fields: Option<Vec<String>>,
150 pub exclude: Vec<String>,
152 pub widgets: HashMap<String, crate::Widget>,
154 pub labels: HashMap<String, String>,
156 pub help_texts: HashMap<String, String>,
158}
159
160impl ModelFormConfig {
161 pub fn new() -> Self {
163 Self::default()
164 }
165 pub fn fields(mut self, fields: Vec<String>) -> Self {
167 self.fields = Some(fields);
168 self
169 }
170 pub fn exclude(mut self, exclude: Vec<String>) -> Self {
172 self.exclude = exclude;
173 self
174 }
175 pub fn widget(mut self, field: String, widget: crate::Widget) -> Self {
177 self.widgets.insert(field, widget);
178 self
179 }
180 pub fn label(mut self, field: String, label: String) -> Self {
182 self.labels.insert(field, label);
183 self
184 }
185 pub fn help_text(mut self, field: String, text: String) -> Self {
187 self.help_texts.insert(field, text);
188 self
189 }
190}
191
192pub struct ModelForm<T: FormModel> {
194 form: Form,
195 instance: Option<T>,
196 #[allow(dead_code)]
198 config: ModelFormConfig,
199 _phantom: PhantomData<T>,
200}
201
202impl<T: FormModel> ModelForm<T> {
203 fn create_form_field(
205 name: &str,
206 field_type: FieldType,
207 config: &ModelFormConfig,
208 ) -> Box<dyn FormField> {
209 let label = config.labels.get(name).cloned();
210 let help_text = config.help_texts.get(name).cloned();
211 let widget = config.widgets.get(name).cloned();
212
213 match field_type {
214 FieldType::Char { max_length } => {
215 let mut field = CharField::new(name.to_string());
216 if let Some(label) = label {
217 field.label = Some(label);
218 }
219 if let Some(help) = help_text {
220 field.help_text = Some(help);
221 }
222 if let Some(w) = widget {
223 field.widget = w;
224 }
225 field.max_length = max_length;
226 Box::new(field)
227 }
228 FieldType::Text => {
229 let mut field = CharField::new(name.to_string());
230 if let Some(label) = label {
231 field.label = Some(label);
232 }
233 if let Some(help) = help_text {
234 field.help_text = Some(help);
235 }
236 if let Some(w) = widget {
237 field.widget = w;
238 } else {
239 field.widget = Widget::TextArea;
240 }
241 Box::new(field)
242 }
243 FieldType::Email => {
244 let mut field = EmailField::new(name.to_string());
245 if let Some(label) = label {
246 field.label = Some(label);
247 }
248 if let Some(help) = help_text {
249 field.help_text = Some(help);
250 }
251 if let Some(w) = widget {
252 field.widget = w;
253 }
254 Box::new(field)
255 }
256 FieldType::Integer => {
257 let mut field = IntegerField::new(name.to_string());
258 if let Some(label) = label {
259 field.label = Some(label);
260 }
261 if let Some(help) = help_text {
262 field.help_text = Some(help);
263 }
264 if let Some(w) = widget {
265 field.widget = w;
266 }
267 Box::new(field)
268 }
269 FieldType::Float => {
270 let mut field = FloatField::new(name.to_string());
271 if let Some(label) = label {
272 field.label = Some(label);
273 }
274 if let Some(help) = help_text {
275 field.help_text = Some(help);
276 }
277 if let Some(w) = widget {
278 field.widget = w;
279 }
280 Box::new(field)
281 }
282 _ => {
284 let mut field = CharField::new(name.to_string());
285 if let Some(label) = label {
286 field.label = Some(label);
287 }
288 if let Some(help) = help_text {
289 field.help_text = Some(help);
290 }
291 if let Some(w) = widget {
292 field.widget = w;
293 }
294 Box::new(field)
295 }
296 }
297 }
298
299 pub fn new(instance: Option<T>, config: ModelFormConfig) -> Self {
311 let mut form = Form::new();
312
313 let all_fields = T::field_names();
315
316 let fields_to_include: Vec<String> = if let Some(ref include) = config.fields {
318 include
319 .iter()
320 .filter(|f| !config.exclude.contains(f))
321 .cloned()
322 .collect()
323 } else {
324 all_fields
325 .iter()
326 .filter(|f| !config.exclude.contains(f))
327 .cloned()
328 .collect()
329 };
330
331 for field_name in &fields_to_include {
333 if let Some(field_type) = T::field_type(field_name) {
334 let form_field = Self::create_form_field(field_name, field_type, &config);
335 form.add_field(form_field);
336 }
337 }
338
339 if let Some(ref inst) = instance {
341 let mut initial = HashMap::new();
342 for field_name in &fields_to_include {
343 if let Some(value) = inst.get_field(field_name) {
344 initial.insert(field_name.clone(), value);
345 }
346 }
347 form.bind(initial);
348 }
349
350 Self {
351 form,
352 instance,
353 config,
354 _phantom: PhantomData,
355 }
356 }
357 pub fn empty(config: ModelFormConfig) -> Self {
368 Self::new(None, config)
369 }
370 pub fn bind(&mut self, data: HashMap<String, Value>) -> &mut Self {
386 self.form.bind(data);
388 self
389 }
390 pub fn is_valid(&mut self) -> bool {
402 if !self.form.is_valid() {
407 return false;
408 }
409
410 if let Some(ref instance) = self.instance
416 && let Err(messages) = instance.validate()
417 {
418 for message in messages {
419 self.form.add_error(crate::form::ALL_FIELDS_KEY, message);
420 }
421 return false;
422 }
423
424 true
425 }
426 pub fn save(&mut self) -> Result<T, FormError> {
441 if self.instance.is_none() {
444 return Err(FormError::NoInstance);
445 }
446
447 if !self.is_valid() {
448 return Err(FormError::Validation("Form is not valid".to_string()));
449 }
450
451 let mut instance = self.instance.take().ok_or(FormError::NoInstance)?;
455
456 let cleaned_data = self.form.cleaned_data();
458 for (field_name, value) in cleaned_data.iter() {
459 if let Err(e) = instance.set_field(field_name, value.clone()) {
460 return Err(FormError::Validation(format!(
461 "Failed to set field {}: {}",
462 field_name, e
463 )));
464 }
465 }
466
467 if let Err(e) = instance.save() {
469 return Err(FormError::Validation(format!("Failed to save: {}", e)));
470 }
471
472 Ok(instance)
473 }
474 pub fn set_field_value(&mut self, field_name: &str, value: Value) {
481 if let Some(ref mut instance) = self.instance {
482 let _ = instance.set_field(field_name, value);
485 }
486 }
487
488 pub fn form(&self) -> &Form {
490 &self.form
491 }
492 pub fn form_mut(&mut self) -> &mut Form {
494 &mut self.form
495 }
496 pub fn instance(&self) -> Option<&T> {
498 self.instance.as_ref()
499 }
500}
501
502pub struct ModelFormBuilder<T: FormModel> {
504 config: ModelFormConfig,
505 _phantom: PhantomData<T>,
506}
507
508impl<T: FormModel> ModelFormBuilder<T> {
509 pub fn new() -> Self {
511 Self {
512 config: ModelFormConfig::default(),
513 _phantom: PhantomData,
514 }
515 }
516 pub fn fields(mut self, fields: Vec<String>) -> Self {
518 self.config.fields = Some(fields);
519 self
520 }
521 pub fn exclude(mut self, exclude: Vec<String>) -> Self {
523 self.config.exclude = exclude;
524 self
525 }
526 pub fn widget(mut self, field: String, widget: crate::Widget) -> Self {
528 self.config.widgets.insert(field, widget);
529 self
530 }
531 pub fn label(mut self, field: String, label: String) -> Self {
533 self.config.labels.insert(field, label);
534 self
535 }
536 pub fn help_text(mut self, field: String, text: String) -> Self {
538 self.config.help_texts.insert(field, text);
539 self
540 }
541 pub fn build(self, instance: Option<T>) -> ModelForm<T> {
553 ModelForm::new(instance, self.config)
554 }
555}
556
557impl<T: FormModel> Default for ModelFormBuilder<T> {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566 use rstest::rstest;
567
568 #[derive(Debug)]
570 struct TestModel {
571 id: i32,
572 name: String,
573 email: String,
574 }
575
576 impl FormModel for TestModel {
577 fn field_names() -> Vec<String> {
578 vec!["id".to_string(), "name".to_string(), "email".to_string()]
579 }
580
581 fn field_type(name: &str) -> Option<FieldType> {
582 match name {
583 "id" => Some(FieldType::Integer),
584 "name" => Some(FieldType::Char {
585 max_length: Some(100),
586 }),
587 "email" => Some(FieldType::Email),
588 _ => None,
589 }
590 }
591
592 fn get_field(&self, name: &str) -> Option<Value> {
593 match name {
594 "id" => Some(Value::Number(self.id.into())),
595 "name" => Some(Value::String(self.name.clone())),
596 "email" => Some(Value::String(self.email.clone())),
597 _ => None,
598 }
599 }
600
601 fn set_field(&mut self, name: &str, value: Value) -> Result<(), String> {
602 match name {
603 "id" => {
604 if let Value::Number(n) = value {
605 self.id = n.as_i64().unwrap() as i32;
606 Ok(())
607 } else {
608 Err("Invalid type for id".to_string())
609 }
610 }
611 "name" => {
612 if let Value::String(s) = value {
613 self.name = s;
614 Ok(())
615 } else {
616 Err("Invalid type for name".to_string())
617 }
618 }
619 "email" => {
620 if let Value::String(s) = value {
621 self.email = s;
622 Ok(())
623 } else {
624 Err("Invalid type for email".to_string())
625 }
626 }
627 _ => Err(format!("Unknown field: {}", name)),
628 }
629 }
630
631 fn save(&mut self) -> Result<(), String> {
632 Ok(())
634 }
635 }
636
637 #[rstest]
638 fn test_model_form_config() {
639 let config = ModelFormConfig::new()
641 .fields(vec!["name".to_string(), "email".to_string()])
642 .exclude(vec!["id".to_string()]);
643
644 assert_eq!(
646 config.fields,
647 Some(vec!["name".to_string(), "email".to_string()])
648 );
649 assert_eq!(config.exclude, vec!["id".to_string()]);
650 }
651
652 #[rstest]
653 fn test_model_form_builder() {
654 let instance = TestModel {
656 id: 1,
657 name: "John".to_string(),
658 email: "john@example.com".to_string(),
659 };
660
661 let form = ModelFormBuilder::<TestModel>::new()
663 .fields(vec!["name".to_string(), "email".to_string()])
664 .build(Some(instance));
665
666 assert!(form.instance().is_some());
668 }
669
670 #[rstest]
671 fn test_model_field_names() {
672 let fields = TestModel::field_names();
674
675 assert_eq!(
677 fields,
678 vec!["id".to_string(), "name".to_string(), "email".to_string()]
679 );
680 }
681
682 #[derive(Debug)]
685 struct AlwaysInvalidModel;
686
687 impl FormModel for AlwaysInvalidModel {
688 fn field_names() -> Vec<String> {
689 vec![]
690 }
691
692 fn get_field(&self, _name: &str) -> Option<Value> {
693 None
694 }
695
696 fn set_field(&mut self, _name: &str, _value: Value) -> Result<(), String> {
697 Ok(())
698 }
699
700 fn save(&mut self) -> Result<(), String> {
701 Ok(())
702 }
703
704 fn validate(&self) -> Result<(), Vec<String>> {
705 Err(vec!["cross-field invariant violated".to_string()])
706 }
707 }
708
709 #[rstest]
710 fn test_is_valid_rejects_unbound_form() {
711 let mut form = ModelForm::<TestModel>::empty(ModelFormConfig::new());
716
717 let actual = form.is_valid();
719
720 assert!(!actual, "Unbound ModelForm must not be valid");
722 }
723
724 #[rstest]
725 fn test_is_valid_rejects_invalid_email_field() {
726 let instance = TestModel {
728 id: 1,
729 name: "John".to_string(),
730 email: "john@example.com".to_string(),
731 };
732 let mut form = ModelForm::new(Some(instance), ModelFormConfig::new());
733 let mut data = HashMap::new();
734 data.insert("id".to_string(), Value::Number(1.into()));
735 data.insert("name".to_string(), Value::String("John".to_string()));
736 data.insert(
737 "email".to_string(),
738 Value::String("not-an-email".to_string()),
739 );
740 form.bind(data);
741
742 let actual = form.is_valid();
744
745 assert!(
747 !actual,
748 "ModelForm::is_valid must reject malformed email values"
749 );
750 }
751
752 #[rstest]
753 fn test_is_valid_accepts_valid_bound_data() {
754 let instance = TestModel {
756 id: 1,
757 name: "John".to_string(),
758 email: "john@example.com".to_string(),
759 };
760 let mut form = ModelForm::new(Some(instance), ModelFormConfig::new());
761 let mut data = HashMap::new();
762 data.insert("id".to_string(), Value::Number(2.into()));
763 data.insert("name".to_string(), Value::String("Jane".to_string()));
764 data.insert(
765 "email".to_string(),
766 Value::String("jane@example.com".to_string()),
767 );
768 form.bind(data);
769
770 let actual = form.is_valid();
772
773 assert!(actual, "Valid bound data must pass validation");
775 }
776
777 #[rstest]
778 fn test_is_valid_consults_model_validate_hook() {
779 let mut form = ModelForm::new(Some(AlwaysInvalidModel), ModelFormConfig::new());
784 form.bind(HashMap::new());
785
786 let actual = form.is_valid();
788
789 assert!(
791 !actual,
792 "ModelForm::is_valid must propagate FormModel::validate errors"
793 );
794 let non_field_errors = form
795 .form()
796 .errors()
797 .get(crate::form::ALL_FIELDS_KEY)
798 .expect("model validate errors must be captured under ALL_FIELDS_KEY");
799 assert_eq!(
800 non_field_errors,
801 &vec!["cross-field invariant violated".to_string()],
802 "FormModel::validate messages must be attached to the form's non-field error bucket"
803 );
804 }
805
806 #[rstest]
807 fn test_save_without_instance_returns_no_instance_error() {
808 let config = ModelFormConfig::new();
810 let mut form = ModelForm::<TestModel>::empty(config);
811
812 let result = form.save();
814
815 assert!(result.is_err());
817 let err = result.unwrap_err();
818 assert!(
819 matches!(err, FormError::NoInstance),
820 "Expected FormError::NoInstance, got: {err}"
821 );
822 }
823}