1use crate::bound_field::BoundField;
2use crate::field::{FieldError, FormField};
3use crate::wasm_compat::ValidationRule;
4use std::collections::HashMap;
5use std::ops::Index;
6
7fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
9 if a.len() != b.len() {
10 return false;
11 }
12 let mut result = 0u8;
13 for (x, y) in a.iter().zip(b.iter()) {
14 result |= x ^ y;
15 }
16 result == 0
17}
18
19#[derive(Debug, thiserror::Error)]
20pub enum FormError {
21 #[error("Field error in {field}: {error}")]
22 Field { field: String, error: FieldError },
23 #[error("Validation error: {0}")]
24 Validation(String),
25 #[error("No model instance available for save operation")]
26 NoInstance,
27}
28
29pub type FormResult<T> = Result<T, FormError>;
30
31type CleanFunction =
32 Box<dyn Fn(&HashMap<String, serde_json::Value>) -> FormResult<()> + Send + Sync>;
33type FieldCleanFunction =
34 Box<dyn Fn(&serde_json::Value) -> FormResult<serde_json::Value> + Send + Sync>;
35
36pub const ALL_FIELDS_KEY: &str = "_all";
41
42pub struct Form {
44 fields: Vec<Box<dyn FormField>>,
45 data: HashMap<String, serde_json::Value>,
46 initial: HashMap<String, serde_json::Value>,
47 errors: HashMap<String, Vec<String>>,
48 is_bound: bool,
49 clean_functions: Vec<CleanFunction>,
50 field_clean_functions: HashMap<String, FieldCleanFunction>,
51 prefix: String,
52 validation_rules: Vec<ValidationRule>,
56 csrf_token: Option<String>,
58 csrf_enabled: bool,
60}
61
62impl Form {
63 pub fn new() -> Self {
75 Self {
76 fields: vec![],
77 data: HashMap::new(),
78 initial: HashMap::new(),
79 errors: HashMap::new(),
80 is_bound: false,
81 clean_functions: vec![],
82 field_clean_functions: HashMap::new(),
83 prefix: String::new(),
84 validation_rules: vec![],
85 csrf_token: None,
86 csrf_enabled: false,
87 }
88 }
89 pub fn with_initial(initial: HashMap<String, serde_json::Value>) -> Self {
105 Self {
106 fields: vec![],
107 data: HashMap::new(),
108 initial,
109 errors: HashMap::new(),
110 is_bound: false,
111 clean_functions: vec![],
112 field_clean_functions: HashMap::new(),
113 prefix: String::new(),
114 validation_rules: vec![],
115 csrf_token: None,
116 csrf_enabled: false,
117 }
118 }
119 pub fn with_prefix(prefix: String) -> Self {
131 Self {
132 fields: vec![],
133 data: HashMap::new(),
134 initial: HashMap::new(),
135 errors: HashMap::new(),
136 is_bound: false,
137 clean_functions: vec![],
138 field_clean_functions: HashMap::new(),
139 prefix,
140 validation_rules: vec![],
141 csrf_token: None,
142 csrf_enabled: false,
143 }
144 }
145 pub fn add_field(&mut self, field: Box<dyn FormField>) {
158 self.fields.push(field);
159 }
160 pub fn bind(&mut self, data: HashMap<String, serde_json::Value>) {
177 self.data = data;
178 self.is_bound = true;
179 }
180 pub fn is_valid(&mut self) -> bool {
201 if !self.is_bound {
202 return false;
203 }
204
205 self.errors.clear();
206
207 if !self.validate_csrf() {
209 self.errors
210 .entry(ALL_FIELDS_KEY.to_string())
211 .or_default()
212 .push("CSRF token missing or incorrect.".to_string());
213 return false;
214 }
215
216 for field in &self.fields {
217 let value = self.data.get(field.name());
218
219 match field.clean(value) {
220 Ok(mut cleaned) => {
221 if let Some(field_clean) = self.field_clean_functions.get(field.name()) {
223 match field_clean(&cleaned) {
224 Ok(further_cleaned) => {
225 cleaned = further_cleaned;
226 }
227 Err(e) => {
228 self.errors
229 .entry(field.name().to_string())
230 .or_default()
231 .push(e.to_string());
232 continue;
233 }
234 }
235 }
236 self.data.insert(field.name().to_string(), cleaned);
237 }
238 Err(e) => {
239 self.errors
240 .entry(field.name().to_string())
241 .or_default()
242 .push(e.to_string());
243 }
244 }
245 }
246
247 for clean_fn in &self.clean_functions {
249 if let Err(e) = clean_fn(&self.data) {
250 match e {
251 FormError::Field { field, error } => {
252 self.errors
253 .entry(field)
254 .or_default()
255 .push(error.to_string());
256 }
257 FormError::Validation(msg) => {
258 self.errors
259 .entry(ALL_FIELDS_KEY.to_string())
260 .or_default()
261 .push(msg);
262 }
263 FormError::NoInstance => {
264 self.errors
265 .entry(ALL_FIELDS_KEY.to_string())
266 .or_default()
267 .push(e.to_string());
268 }
269 }
270 }
271 }
272
273 self.errors.is_empty()
274 }
275 pub fn cleaned_data(&self) -> &HashMap<String, serde_json::Value> {
276 &self.data
277 }
278 pub fn errors(&self) -> &HashMap<String, Vec<String>> {
279 &self.errors
280 }
281 pub fn is_bound(&self) -> bool {
282 self.is_bound
283 }
284 pub fn fields(&self) -> &[Box<dyn FormField>] {
285 &self.fields
286 }
287 pub fn initial(&self) -> &HashMap<String, serde_json::Value> {
288 &self.initial
289 }
290 pub fn set_initial(&mut self, initial: HashMap<String, serde_json::Value>) {
305 self.initial = initial;
306 }
307 pub fn has_changed(&self) -> bool {
329 if !self.is_bound {
330 return false;
331 }
332
333 for field in &self.fields {
334 let initial_val = self.initial.get(field.name());
335 let data_val = self.data.get(field.name());
336 if field.has_changed(initial_val, data_val) {
337 return true;
338 }
339 }
340 false
341 }
342 pub fn get_field(&self, name: &str) -> Option<&dyn FormField> {
343 self.fields
344 .iter()
345 .find(|f| f.name() == name)
346 .map(|f| f.as_ref())
347 }
348 pub fn remove_field(&mut self, name: &str) -> Option<Box<dyn FormField>> {
349 let pos = self.fields.iter().position(|f| f.name() == name)?;
350 Some(self.fields.remove(pos))
351 }
352 pub fn field_count(&self) -> usize {
353 self.fields.len()
354 }
355 pub fn add_clean_function<F>(&mut self, f: F)
374 where
375 F: Fn(&HashMap<String, serde_json::Value>) -> FormResult<()> + Send + Sync + 'static,
376 {
377 self.clean_functions.push(Box::new(f));
378 }
379 pub fn add_field_clean_function<F>(&mut self, field_name: &str, f: F)
401 where
402 F: Fn(&serde_json::Value) -> FormResult<serde_json::Value> + Send + Sync + 'static,
403 {
404 self.field_clean_functions
405 .insert(field_name.to_string(), Box::new(f));
406 }
407
408 pub fn validation_rules(&self) -> &[ValidationRule] {
414 &self.validation_rules
415 }
416
417 pub fn add_min_length_validator(
440 &mut self,
441 field_name: impl Into<String>,
442 min: usize,
443 error_message: impl Into<String>,
444 ) {
445 self.validation_rules.push(ValidationRule::MinLength {
446 field_name: field_name.into(),
447 min,
448 error_message: error_message.into(),
449 });
450 }
451
452 pub fn add_max_length_validator(
465 &mut self,
466 field_name: impl Into<String>,
467 max: usize,
468 error_message: impl Into<String>,
469 ) {
470 self.validation_rules.push(ValidationRule::MaxLength {
471 field_name: field_name.into(),
472 max,
473 error_message: error_message.into(),
474 });
475 }
476
477 pub fn add_pattern_validator(
490 &mut self,
491 field_name: impl Into<String>,
492 pattern: impl Into<String>,
493 error_message: impl Into<String>,
494 ) {
495 self.validation_rules.push(ValidationRule::Pattern {
496 field_name: field_name.into(),
497 pattern: pattern.into(),
498 error_message: error_message.into(),
499 });
500 }
501
502 pub fn add_min_value_validator(
515 &mut self,
516 field_name: impl Into<String>,
517 min: f64,
518 error_message: impl Into<String>,
519 ) {
520 self.validation_rules.push(ValidationRule::MinValue {
521 field_name: field_name.into(),
522 min,
523 error_message: error_message.into(),
524 });
525 }
526
527 pub fn add_max_value_validator(
540 &mut self,
541 field_name: impl Into<String>,
542 max: f64,
543 error_message: impl Into<String>,
544 ) {
545 self.validation_rules.push(ValidationRule::MaxValue {
546 field_name: field_name.into(),
547 max,
548 error_message: error_message.into(),
549 });
550 }
551
552 pub fn add_email_validator(
565 &mut self,
566 field_name: impl Into<String>,
567 error_message: impl Into<String>,
568 ) {
569 self.validation_rules.push(ValidationRule::Email {
570 field_name: field_name.into(),
571 error_message: error_message.into(),
572 });
573 }
574
575 pub fn add_url_validator(
588 &mut self,
589 field_name: impl Into<String>,
590 error_message: impl Into<String>,
591 ) {
592 self.validation_rules.push(ValidationRule::Url {
593 field_name: field_name.into(),
594 error_message: error_message.into(),
595 });
596 }
597
598 pub fn add_fields_equal_validator(
622 &mut self,
623 field_names: Vec<String>,
624 error_message: impl Into<String>,
625 target_field: Option<String>,
626 ) {
627 self.validation_rules.push(ValidationRule::FieldsEqual {
628 field_names,
629 error_message: error_message.into(),
630 target_field,
631 });
632 }
633
634 pub fn add_validator_rule(
671 &mut self,
672 field_name: impl Into<String>,
673 validator_id: impl Into<String>,
674 params: serde_json::Value,
675 error_message: impl Into<String>,
676 ) {
677 self.validation_rules.push(ValidationRule::ValidatorRef {
678 field_name: field_name.into(),
679 validator_id: validator_id.into(),
680 params,
681 error_message: error_message.into(),
682 });
683 }
684
685 pub fn add_date_range_validator(
704 &mut self,
705 start_field: impl Into<String>,
706 end_field: impl Into<String>,
707 error_message: Option<String>,
708 ) {
709 let start = start_field.into();
710 let end = end_field.into();
711 let message = error_message
712 .unwrap_or_else(|| "End date must be after or equal to start date".to_string());
713
714 self.validation_rules.push(ValidationRule::DateRange {
715 start_field: start,
716 end_field: end.clone(),
717 error_message: message,
718 target_field: Some(end),
719 });
720 }
721
722 pub fn add_numeric_range_validator(
741 &mut self,
742 min_field: impl Into<String>,
743 max_field: impl Into<String>,
744 error_message: Option<String>,
745 ) {
746 let min = min_field.into();
747 let max = max_field.into();
748 let message = error_message.unwrap_or_else(|| {
749 "Maximum value must be greater than or equal to minimum value".to_string()
750 });
751
752 self.validation_rules.push(ValidationRule::NumericRange {
753 min_field: min,
754 max_field: max.clone(),
755 error_message: message,
756 target_field: Some(max),
757 });
758 }
759 pub fn set_csrf_token(&mut self, token: String) {
778 self.csrf_token = Some(token);
779 self.csrf_enabled = true;
780 }
781
782 pub fn csrf_enabled(&self) -> bool {
784 self.csrf_enabled
785 }
786
787 pub fn csrf_token(&self) -> Option<&str> {
789 self.csrf_token.as_deref()
790 }
791
792 fn validate_csrf(&self) -> bool {
796 if !self.csrf_enabled {
797 return true;
798 }
799
800 let expected = match &self.csrf_token {
801 Some(t) => t,
802 None => return false,
803 };
804
805 let submitted = self
806 .data
807 .get("csrfmiddlewaretoken")
808 .and_then(|v| v.as_str());
809
810 match submitted {
811 Some(token) => {
812 constant_time_eq(token.as_bytes(), expected.as_bytes())
814 }
815 None => false,
816 }
817 }
818
819 pub fn prefix(&self) -> &str {
820 &self.prefix
821 }
822 pub fn set_prefix(&mut self, prefix: String) {
823 self.prefix = prefix;
824 }
825 pub fn add_prefix_to_field_name(&self, field_name: &str) -> String {
826 if self.prefix.is_empty() {
827 field_name.to_string()
828 } else {
829 format!("{}-{}", self.prefix, field_name)
830 }
831 }
832 pub fn render_css_media(&self, css_files: &[&str]) -> String {
851 use crate::field::escape_attribute;
852 let mut html = String::new();
853 for path in css_files {
854 html.push_str(&format!(
855 "<link rel=\"stylesheet\" href=\"{}\" />\n",
856 escape_attribute(path)
857 ));
858 }
859 html
860 }
861
862 pub fn render_js_media(&self, js_files: &[&str]) -> String {
881 use crate::field::escape_attribute;
882 let mut html = String::new();
883 for path in js_files {
884 html.push_str(&format!(
885 "<script src=\"{}\"></script>\n",
886 escape_attribute(path)
887 ));
888 }
889 html
890 }
891
892 pub fn get_bound_field<'a>(&'a self, name: &str) -> Option<BoundField<'a>> {
893 let field = self.get_field(name)?;
894 let data = self.data.get(name);
895 let errors = self.errors.get(name).map(|e| e.as_slice()).unwrap_or(&[]);
896
897 Some(BoundField::new(
898 "form".to_string(),
899 field,
900 data,
901 errors,
902 &self.prefix,
903 ))
904 }
905}
906
907impl Default for Form {
908 fn default() -> Self {
909 Self::new()
910 }
911}
912
913impl Form {
929 #[allow(clippy::borrowed_box)]
931 pub fn get(&self, name: &str) -> Option<&Box<dyn FormField>> {
932 self.fields.iter().find(|f| f.name() == name)
933 }
934}
935
936impl Index<&str> for Form {
937 type Output = Box<dyn FormField>;
938
939 fn index(&self, name: &str) -> &Self::Output {
940 self.get(name)
941 .unwrap_or_else(|| panic!("Field '{}' not found", name))
942 }
943}
944
945#[cfg(test)]
946mod tests {
947 use super::*;
948 use crate::fields::CharField;
949
950 #[test]
951 fn test_form_validation() {
952 let mut form = Form::new();
953
954 let mut name_field = CharField::new("name".to_string());
955 name_field.max_length = Some(50);
956 form.add_field(Box::new(name_field));
957
958 let mut data = HashMap::new();
959 data.insert("name".to_string(), serde_json::json!("John Doe"));
960
961 form.bind(data);
962 assert!(form.is_valid());
963 assert!(form.errors().is_empty());
964 }
965
966 #[test]
967 fn test_form_validation_error() {
968 let mut form = Form::new();
969
970 let mut name_field = CharField::new("name".to_string());
971 name_field.max_length = Some(5);
972 form.add_field(Box::new(name_field));
973
974 let mut data = HashMap::new();
975 data.insert("name".to_string(), serde_json::json!("Very Long Name"));
976
977 form.bind(data);
978 assert!(!form.is_valid());
979 assert!(!form.errors().is_empty());
980 }
981
982 #[test]
985 fn test_form_basic() {
986 use crate::fields::CharField;
988
989 let mut form = Form::new();
990 form.add_field(Box::new(CharField::new("first_name".to_string())));
991 form.add_field(Box::new(CharField::new("last_name".to_string())));
992
993 let mut data = HashMap::new();
994 data.insert("first_name".to_string(), serde_json::json!("John"));
995 data.insert("last_name".to_string(), serde_json::json!("Lennon"));
996
997 form.bind(data);
998
999 assert!(form.is_bound());
1000 assert!(form.is_valid());
1001 assert!(form.errors().is_empty());
1002
1003 let cleaned = form.cleaned_data();
1005 assert_eq!(
1006 cleaned.get("first_name").unwrap(),
1007 &serde_json::json!("John")
1008 );
1009 assert_eq!(
1010 cleaned.get("last_name").unwrap(),
1011 &serde_json::json!("Lennon")
1012 );
1013 }
1014
1015 #[test]
1016 fn test_form_missing_required_fields() {
1017 use crate::fields::CharField;
1019
1020 let mut form = Form::new();
1021 form.add_field(Box::new(CharField::new("username".to_string()).required()));
1022 form.add_field(Box::new(CharField::new("email".to_string()).required()));
1023
1024 let data = HashMap::new(); form.bind(data);
1027
1028 assert!(form.is_bound());
1029 assert!(!form.is_valid());
1030 assert!(form.errors().contains_key("username"));
1031 assert!(form.errors().contains_key("email"));
1032 }
1033
1034 #[test]
1035 fn test_form_optional_fields() {
1036 use crate::fields::CharField;
1038
1039 let mut form = Form::new();
1040
1041 let username_field = CharField::new("username".to_string());
1042 form.add_field(Box::new(username_field));
1043
1044 let mut bio_field = CharField::new("bio".to_string());
1045 bio_field.required = false;
1046 form.add_field(Box::new(bio_field));
1047
1048 let mut data = HashMap::new();
1049 data.insert("username".to_string(), serde_json::json!("john"));
1050 form.bind(data);
1053
1054 assert!(form.is_bound());
1055 assert!(form.is_valid());
1056 assert!(form.errors().is_empty());
1057 }
1058
1059 #[test]
1060 fn test_form_unbound() {
1061 use crate::fields::CharField;
1063
1064 let mut form = Form::new();
1065 form.add_field(Box::new(CharField::new("name".to_string())));
1066
1067 assert!(!form.is_bound());
1068 assert!(!form.is_valid()); }
1070
1071 #[test]
1072 fn test_form_extra_data() {
1073 use crate::fields::CharField;
1075
1076 let mut form = Form::new();
1077 form.add_field(Box::new(CharField::new("name".to_string())));
1078
1079 let mut data = HashMap::new();
1080 data.insert("name".to_string(), serde_json::json!("John"));
1081 data.insert(
1082 "extra_field".to_string(),
1083 serde_json::json!("should be ignored"),
1084 );
1085
1086 form.bind(data);
1087
1088 assert!(form.is_valid());
1089 let cleaned = form.cleaned_data();
1090 assert_eq!(cleaned.get("name").unwrap(), &serde_json::json!("John"));
1091 assert!(cleaned.contains_key("extra_field"));
1093 }
1094
1095 #[test]
1096 fn test_forms_form_multiple_fields() {
1097 use crate::fields::{CharField, IntegerField};
1099
1100 let mut form = Form::new();
1101 form.add_field(Box::new(CharField::new("username".to_string())));
1102
1103 let mut age_field = IntegerField::new("age".to_string());
1104 age_field.min_value = Some(0);
1105 age_field.max_value = Some(150);
1106 form.add_field(Box::new(age_field));
1107
1108 let mut data = HashMap::new();
1109 data.insert("username".to_string(), serde_json::json!("alice"));
1110 data.insert("age".to_string(), serde_json::json!(30));
1111
1112 form.bind(data);
1113
1114 assert!(form.is_valid());
1115 assert!(form.errors().is_empty());
1116 }
1117
1118 #[test]
1119 fn test_form_multiple_fields_invalid() {
1120 use crate::fields::{CharField, IntegerField};
1122
1123 let mut form = Form::new();
1124
1125 let mut username_field = CharField::new("username".to_string());
1126 username_field.min_length = Some(3);
1127 form.add_field(Box::new(username_field));
1128
1129 let mut age_field = IntegerField::new("age".to_string());
1130 age_field.min_value = Some(0);
1131 age_field.max_value = Some(150);
1132 form.add_field(Box::new(age_field));
1133
1134 let mut data = HashMap::new();
1135 data.insert("username".to_string(), serde_json::json!("ab")); data.insert("age".to_string(), serde_json::json!(200)); form.bind(data);
1139
1140 assert!(!form.is_valid());
1141 assert!(form.errors().contains_key("username"));
1142 assert!(form.errors().contains_key("age"));
1143 }
1144
1145 #[test]
1146 fn test_form_multiple_instances() {
1147 use crate::fields::CharField;
1149
1150 let mut form1 = Form::new();
1151 form1.add_field(Box::new(CharField::new("name".to_string())));
1152
1153 let mut form2 = Form::new();
1154 form2.add_field(Box::new(CharField::new("name".to_string())));
1155
1156 let mut data1 = HashMap::new();
1157 data1.insert("name".to_string(), serde_json::json!("Form1"));
1158 form1.bind(data1);
1159
1160 let mut data2 = HashMap::new();
1161 data2.insert("name".to_string(), serde_json::json!("Form2"));
1162 form2.bind(data2);
1163
1164 assert!(form1.is_valid());
1165 assert!(form2.is_valid());
1166
1167 assert_eq!(
1168 form1.cleaned_data().get("name").unwrap(),
1169 &serde_json::json!("Form1")
1170 );
1171 assert_eq!(
1172 form2.cleaned_data().get("name").unwrap(),
1173 &serde_json::json!("Form2")
1174 );
1175 }
1176
1177 #[test]
1178 fn test_form_with_initial_data() {
1179 let mut initial = HashMap::new();
1180 initial.insert("name".to_string(), serde_json::json!("Initial Name"));
1181 initial.insert("age".to_string(), serde_json::json!(25));
1182
1183 let mut form = Form::with_initial(initial);
1184
1185 let name_field = CharField::new("name".to_string());
1186 form.add_field(Box::new(name_field));
1187
1188 let age_field = crate::IntegerField::new("age".to_string());
1189 form.add_field(Box::new(age_field));
1190
1191 assert_eq!(
1192 form.initial().get("name").unwrap(),
1193 &serde_json::json!("Initial Name")
1194 );
1195 assert_eq!(form.initial().get("age").unwrap(), &serde_json::json!(25));
1196 }
1197
1198 #[test]
1199 fn test_form_has_changed() {
1200 let mut initial = HashMap::new();
1201 initial.insert("name".to_string(), serde_json::json!("John"));
1202
1203 let mut form = Form::with_initial(initial);
1204
1205 let name_field = CharField::new("name".to_string());
1206 form.add_field(Box::new(name_field));
1207
1208 let mut data1 = HashMap::new();
1210 data1.insert("name".to_string(), serde_json::json!("John"));
1211 form.bind(data1);
1212 assert!(!form.has_changed());
1213
1214 let mut data2 = HashMap::new();
1216 data2.insert("name".to_string(), serde_json::json!("Jane"));
1217 form.bind(data2);
1218 assert!(form.has_changed());
1219 }
1220
1221 #[test]
1222 fn test_form_index_access() {
1223 let mut form = Form::new();
1224
1225 let name_field = CharField::new("name".to_string());
1226 form.add_field(Box::new(name_field));
1227
1228 let field = &form["name"];
1229 assert_eq!(field.name(), "name");
1230 }
1231
1232 #[test]
1233 #[should_panic(expected = "Field 'nonexistent' not found")]
1234 fn test_form_index_access_nonexistent() {
1235 let form = Form::new();
1236 let _ = &form["nonexistent"];
1237 }
1238
1239 #[test]
1240 fn test_form_get_field() {
1241 let mut form = Form::new();
1242
1243 let name_field = CharField::new("name".to_string());
1244 form.add_field(Box::new(name_field));
1245
1246 assert!(form.get_field("name").is_some());
1247 assert!(form.get_field("nonexistent").is_none());
1248 }
1249
1250 #[test]
1251 fn test_form_remove_field() {
1252 let mut form = Form::new();
1253
1254 let name_field = CharField::new("name".to_string());
1255 form.add_field(Box::new(name_field));
1256
1257 assert_eq!(form.field_count(), 1);
1258
1259 let removed = form.remove_field("name");
1260 assert!(removed.is_some());
1261 assert_eq!(form.field_count(), 0);
1262
1263 let not_removed = form.remove_field("nonexistent");
1264 assert!(not_removed.is_none());
1265 }
1266
1267 #[test]
1268 fn test_form_custom_validation() {
1269 let mut form = Form::new();
1270
1271 let mut password_field = CharField::new("password".to_string());
1272 password_field.min_length = Some(8);
1273 form.add_field(Box::new(password_field));
1274
1275 let mut confirm_field = CharField::new("confirm".to_string());
1276 confirm_field.min_length = Some(8);
1277 form.add_field(Box::new(confirm_field));
1278
1279 form.add_clean_function(|data| {
1281 let password = data.get("password").and_then(|v| v.as_str());
1282 let confirm = data.get("confirm").and_then(|v| v.as_str());
1283
1284 if password != confirm {
1285 return Err(FormError::Validation("Passwords do not match".to_string()));
1286 }
1287
1288 Ok(())
1289 });
1290
1291 let mut data1 = HashMap::new();
1293 data1.insert("password".to_string(), serde_json::json!("secret123"));
1294 data1.insert("confirm".to_string(), serde_json::json!("secret123"));
1295 form.bind(data1);
1296 assert!(form.is_valid());
1297
1298 let mut data2 = HashMap::new();
1300 data2.insert("password".to_string(), serde_json::json!("secret123"));
1301 data2.insert("confirm".to_string(), serde_json::json!("different"));
1302 form.bind(data2);
1303 assert!(!form.is_valid());
1304 assert!(form.errors().contains_key(ALL_FIELDS_KEY));
1305 }
1306
1307 #[test]
1308 fn test_form_prefix() {
1309 let mut form = Form::with_prefix("profile".to_string());
1310 assert_eq!(form.prefix(), "profile");
1311 assert_eq!(form.add_prefix_to_field_name("name"), "profile-name");
1312
1313 form.set_prefix("user".to_string());
1314 assert_eq!(form.prefix(), "user");
1315 assert_eq!(form.add_prefix_to_field_name("email"), "user-email");
1316 }
1317
1318 #[test]
1319 fn test_form_field_clean_function() {
1320 let mut form = Form::new();
1321
1322 let mut name_field = CharField::new("name".to_string());
1323 name_field.required = true;
1324 form.add_field(Box::new(name_field));
1325
1326 form.add_field_clean_function("name", |value| {
1328 if let Some(s) = value.as_str() {
1329 Ok(serde_json::json!(s.to_uppercase()))
1330 } else {
1331 Err(FormError::Validation("Expected string".to_string()))
1332 }
1333 });
1334
1335 let mut data = HashMap::new();
1336 data.insert("name".to_string(), serde_json::json!("john doe"));
1337 form.bind(data);
1338
1339 assert!(form.is_valid());
1340 assert_eq!(
1341 form.cleaned_data().get("name").unwrap(),
1342 &serde_json::json!("JOHN DOE")
1343 );
1344 }
1345}