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 {
13 use sha2::{Digest, Sha256};
14 use subtle::ConstantTimeEq;
15
16 let hash_a = Sha256::digest(a);
17 let hash_b = Sha256::digest(b);
18 hash_a.ct_eq(&hash_b).into()
19}
20
21#[derive(Debug, thiserror::Error)]
23pub enum FormError {
24 #[error("Field error in {field}: {error}")]
26 Field {
27 field: String,
29 error: FieldError,
31 },
32 #[error("Validation error: {0}")]
34 Validation(String),
35 #[error("No model instance available for save operation")]
37 NoInstance,
38}
39
40pub type FormResult<T> = Result<T, FormError>;
42
43type CleanFunction =
44 Box<dyn Fn(&HashMap<String, serde_json::Value>) -> FormResult<()> + Send + Sync>;
45type FieldCleanFunction =
46 Box<dyn Fn(&serde_json::Value) -> FormResult<serde_json::Value> + Send + Sync>;
47
48pub const ALL_FIELDS_KEY: &str = "_all";
53
54pub struct Form {
56 fields: Vec<Box<dyn FormField>>,
57 data: HashMap<String, serde_json::Value>,
58 initial: HashMap<String, serde_json::Value>,
59 errors: HashMap<String, Vec<String>>,
60 is_bound: bool,
61 clean_functions: Vec<CleanFunction>,
62 field_clean_functions: HashMap<String, FieldCleanFunction>,
63 prefix: String,
64 validation_rules: Vec<ValidationRule>,
68 csrf_token: Option<String>,
70 csrf_enabled: bool,
72}
73
74impl Form {
75 pub fn new() -> Self {
87 Self {
88 fields: vec![],
89 data: HashMap::new(),
90 initial: HashMap::new(),
91 errors: HashMap::new(),
92 is_bound: false,
93 clean_functions: vec![],
94 field_clean_functions: HashMap::new(),
95 prefix: String::new(),
96 validation_rules: vec![],
97 csrf_token: None,
98 csrf_enabled: false,
99 }
100 }
101 pub fn with_initial(initial: HashMap<String, serde_json::Value>) -> Self {
117 Self {
118 fields: vec![],
119 data: HashMap::new(),
120 initial,
121 errors: HashMap::new(),
122 is_bound: false,
123 clean_functions: vec![],
124 field_clean_functions: HashMap::new(),
125 prefix: String::new(),
126 validation_rules: vec![],
127 csrf_token: None,
128 csrf_enabled: false,
129 }
130 }
131 pub fn with_prefix(prefix: String) -> Self {
143 Self {
144 fields: vec![],
145 data: HashMap::new(),
146 initial: HashMap::new(),
147 errors: HashMap::new(),
148 is_bound: false,
149 clean_functions: vec![],
150 field_clean_functions: HashMap::new(),
151 prefix,
152 validation_rules: vec![],
153 csrf_token: None,
154 csrf_enabled: false,
155 }
156 }
157 pub fn add_field(&mut self, field: Box<dyn FormField>) {
170 self.fields.push(field);
171 }
172 pub fn bind(&mut self, data: HashMap<String, serde_json::Value>) {
189 self.data = data;
190 self.is_bound = true;
191 }
192 pub fn is_valid(&mut self) -> bool {
213 if !self.is_bound {
214 return false;
215 }
216
217 self.errors.clear();
218
219 if !self.validate_csrf() {
221 self.errors
222 .entry(ALL_FIELDS_KEY.to_string())
223 .or_default()
224 .push("CSRF token missing or incorrect.".to_string());
225 return false;
226 }
227
228 for field in &self.fields {
229 let value = self.data.get(field.name());
230
231 match field.clean(value) {
232 Ok(mut cleaned) => {
233 if let Some(field_clean) = self.field_clean_functions.get(field.name()) {
235 match field_clean(&cleaned) {
236 Ok(further_cleaned) => {
237 cleaned = further_cleaned;
238 }
239 Err(e) => {
240 self.errors
241 .entry(field.name().to_string())
242 .or_default()
243 .push(e.to_string());
244 continue;
245 }
246 }
247 }
248 self.data.insert(field.name().to_string(), cleaned);
249 }
250 Err(e) => {
251 self.errors
252 .entry(field.name().to_string())
253 .or_default()
254 .push(e.to_string());
255 }
256 }
257 }
258
259 for clean_fn in &self.clean_functions {
261 if let Err(e) = clean_fn(&self.data) {
262 match e {
263 FormError::Field { field, error } => {
264 self.errors
265 .entry(field)
266 .or_default()
267 .push(error.to_string());
268 }
269 FormError::Validation(msg) => {
270 self.errors
271 .entry(ALL_FIELDS_KEY.to_string())
272 .or_default()
273 .push(msg);
274 }
275 FormError::NoInstance => {
276 self.errors
277 .entry(ALL_FIELDS_KEY.to_string())
278 .or_default()
279 .push(e.to_string());
280 }
281 }
282 }
283 }
284
285 self.errors.is_empty()
286 }
287 pub fn cleaned_data(&self) -> &HashMap<String, serde_json::Value> {
289 &self.data
290 }
291 pub fn errors(&self) -> &HashMap<String, Vec<String>> {
293 &self.errors
294 }
295 pub fn add_error(&mut self, field_name: impl Into<String>, message: impl Into<String>) {
300 self.errors
301 .entry(field_name.into())
302 .or_default()
303 .push(message.into());
304 }
305 pub fn is_bound(&self) -> bool {
307 self.is_bound
308 }
309 pub fn fields(&self) -> &[Box<dyn FormField>] {
311 &self.fields
312 }
313 pub fn initial(&self) -> &HashMap<String, serde_json::Value> {
315 &self.initial
316 }
317 pub fn set_initial(&mut self, initial: HashMap<String, serde_json::Value>) {
332 self.initial = initial;
333 }
334 pub fn has_changed(&self) -> bool {
356 if !self.is_bound {
357 return false;
358 }
359
360 for field in &self.fields {
361 let initial_val = self.initial.get(field.name());
362 let data_val = self.data.get(field.name());
363 if field.has_changed(initial_val, data_val) {
364 return true;
365 }
366 }
367 false
368 }
369 pub fn get_field(&self, name: &str) -> Option<&dyn FormField> {
371 self.fields
372 .iter()
373 .find(|f| f.name() == name)
374 .map(|f| f.as_ref())
375 }
376 pub fn remove_field(&mut self, name: &str) -> Option<Box<dyn FormField>> {
378 let pos = self.fields.iter().position(|f| f.name() == name)?;
379 Some(self.fields.remove(pos))
380 }
381 pub fn field_count(&self) -> usize {
383 self.fields.len()
384 }
385 pub fn add_clean_function<F>(&mut self, f: F)
404 where
405 F: Fn(&HashMap<String, serde_json::Value>) -> FormResult<()> + Send + Sync + 'static,
406 {
407 self.clean_functions.push(Box::new(f));
408 }
409 pub fn add_field_clean_function<F>(&mut self, field_name: &str, f: F)
431 where
432 F: Fn(&serde_json::Value) -> FormResult<serde_json::Value> + Send + Sync + 'static,
433 {
434 self.field_clean_functions
435 .insert(field_name.to_string(), Box::new(f));
436 }
437
438 pub fn validation_rules(&self) -> &[ValidationRule] {
444 &self.validation_rules
445 }
446
447 pub fn add_min_length_validator(
470 &mut self,
471 field_name: impl Into<String>,
472 min: usize,
473 error_message: impl Into<String>,
474 ) {
475 self.validation_rules.push(ValidationRule::MinLength {
476 field_name: field_name.into(),
477 min,
478 error_message: error_message.into(),
479 });
480 }
481
482 pub fn add_max_length_validator(
495 &mut self,
496 field_name: impl Into<String>,
497 max: usize,
498 error_message: impl Into<String>,
499 ) {
500 self.validation_rules.push(ValidationRule::MaxLength {
501 field_name: field_name.into(),
502 max,
503 error_message: error_message.into(),
504 });
505 }
506
507 pub fn add_pattern_validator(
520 &mut self,
521 field_name: impl Into<String>,
522 pattern: impl Into<String>,
523 error_message: impl Into<String>,
524 ) {
525 self.validation_rules.push(ValidationRule::Pattern {
526 field_name: field_name.into(),
527 pattern: pattern.into(),
528 error_message: error_message.into(),
529 });
530 }
531
532 pub fn add_min_value_validator(
545 &mut self,
546 field_name: impl Into<String>,
547 min: f64,
548 error_message: impl Into<String>,
549 ) {
550 self.validation_rules.push(ValidationRule::MinValue {
551 field_name: field_name.into(),
552 min,
553 error_message: error_message.into(),
554 });
555 }
556
557 pub fn add_max_value_validator(
570 &mut self,
571 field_name: impl Into<String>,
572 max: f64,
573 error_message: impl Into<String>,
574 ) {
575 self.validation_rules.push(ValidationRule::MaxValue {
576 field_name: field_name.into(),
577 max,
578 error_message: error_message.into(),
579 });
580 }
581
582 pub fn add_email_validator(
595 &mut self,
596 field_name: impl Into<String>,
597 error_message: impl Into<String>,
598 ) {
599 self.validation_rules.push(ValidationRule::Email {
600 field_name: field_name.into(),
601 error_message: error_message.into(),
602 });
603 }
604
605 pub fn add_url_validator(
618 &mut self,
619 field_name: impl Into<String>,
620 error_message: impl Into<String>,
621 ) {
622 self.validation_rules.push(ValidationRule::Url {
623 field_name: field_name.into(),
624 error_message: error_message.into(),
625 });
626 }
627
628 pub fn add_fields_equal_validator(
652 &mut self,
653 field_names: Vec<String>,
654 error_message: impl Into<String>,
655 target_field: Option<String>,
656 ) {
657 self.validation_rules.push(ValidationRule::FieldsEqual {
658 field_names,
659 error_message: error_message.into(),
660 target_field,
661 });
662 }
663
664 pub fn add_validator_rule(
701 &mut self,
702 field_name: impl Into<String>,
703 validator_id: impl Into<String>,
704 params: serde_json::Value,
705 error_message: impl Into<String>,
706 ) {
707 self.validation_rules.push(ValidationRule::ValidatorRef {
708 field_name: field_name.into(),
709 validator_id: validator_id.into(),
710 params,
711 error_message: error_message.into(),
712 });
713 }
714
715 pub fn add_date_range_validator(
734 &mut self,
735 start_field: impl Into<String>,
736 end_field: impl Into<String>,
737 error_message: Option<String>,
738 ) {
739 let start = start_field.into();
740 let end = end_field.into();
741 let message = error_message
742 .unwrap_or_else(|| "End date must be after or equal to start date".to_string());
743
744 self.validation_rules.push(ValidationRule::DateRange {
745 start_field: start,
746 end_field: end.clone(),
747 error_message: message,
748 target_field: Some(end),
749 });
750 }
751
752 pub fn add_numeric_range_validator(
771 &mut self,
772 min_field: impl Into<String>,
773 max_field: impl Into<String>,
774 error_message: Option<String>,
775 ) {
776 let min = min_field.into();
777 let max = max_field.into();
778 let message = error_message.unwrap_or_else(|| {
779 "Maximum value must be greater than or equal to minimum value".to_string()
780 });
781
782 self.validation_rules.push(ValidationRule::NumericRange {
783 min_field: min,
784 max_field: max.clone(),
785 error_message: message,
786 target_field: Some(max),
787 });
788 }
789 pub fn set_csrf_token(&mut self, token: String) {
808 self.csrf_token = Some(token);
809 self.csrf_enabled = true;
810 }
811
812 pub fn csrf_enabled(&self) -> bool {
814 self.csrf_enabled
815 }
816
817 pub fn csrf_token(&self) -> Option<&str> {
819 self.csrf_token.as_deref()
820 }
821
822 fn validate_csrf(&self) -> bool {
826 if !self.csrf_enabled {
827 return true;
828 }
829
830 let expected = match &self.csrf_token {
831 Some(t) => t,
832 None => return false,
833 };
834
835 let submitted = self
836 .data
837 .get("csrfmiddlewaretoken")
838 .and_then(|v| v.as_str());
839
840 match submitted {
841 Some(token) => {
842 constant_time_eq(token.as_bytes(), expected.as_bytes())
844 }
845 None => false,
846 }
847 }
848
849 pub fn prefix(&self) -> &str {
851 &self.prefix
852 }
853 pub fn set_prefix(&mut self, prefix: String) {
855 self.prefix = prefix;
856 }
857 pub fn add_prefix_to_field_name(&self, field_name: &str) -> String {
859 if self.prefix.is_empty() {
860 field_name.to_string()
861 } else {
862 format!("{}-{}", self.prefix, field_name)
863 }
864 }
865 pub fn render_css_media(&self, css_files: &[&str]) -> String {
884 use crate::field::escape_attribute;
885 let mut html = String::new();
886 for path in css_files {
887 html.push_str(&format!(
888 "<link rel=\"stylesheet\" href=\"{}\" />\n",
889 escape_attribute(path)
890 ));
891 }
892 html
893 }
894
895 pub fn render_js_media(&self, js_files: &[&str]) -> String {
914 use crate::field::escape_attribute;
915 let mut html = String::new();
916 for path in js_files {
917 html.push_str(&format!(
918 "<script src=\"{}\"></script>\n",
919 escape_attribute(path)
920 ));
921 }
922 html
923 }
924
925 pub fn get_bound_field<'a>(&'a self, name: &str) -> Option<BoundField<'a>> {
927 let field = self.get_field(name)?;
928 let data = self.data.get(name);
929 let errors = self.errors.get(name).map(|e| e.as_slice()).unwrap_or(&[]);
930
931 Some(BoundField::new(
932 "form".to_string(),
933 field,
934 data,
935 errors,
936 &self.prefix,
937 ))
938 }
939}
940
941impl Default for Form {
942 fn default() -> Self {
943 Self::new()
944 }
945}
946
947impl Form {
963 #[allow(clippy::borrowed_box)]
965 pub fn get(&self, name: &str) -> Option<&Box<dyn FormField>> {
967 self.fields.iter().find(|f| f.name() == name)
968 }
969}
970
971impl Index<&str> for Form {
972 type Output = Box<dyn FormField>;
973
974 fn index(&self, name: &str) -> &Self::Output {
975 self.get(name)
976 .unwrap_or_else(|| panic!("Field '{}' not found", name))
977 }
978}
979
980#[cfg(test)]
981mod tests {
982 use super::*;
983 use crate::fields::CharField;
984
985 #[test]
986 fn test_form_validation() {
987 let mut form = Form::new();
988
989 let mut name_field = CharField::new("name".to_string());
990 name_field.max_length = Some(50);
991 form.add_field(Box::new(name_field));
992
993 let mut data = HashMap::new();
994 data.insert("name".to_string(), serde_json::json!("John Doe"));
995
996 form.bind(data);
997 assert!(form.is_valid());
998 assert!(form.errors().is_empty());
999 }
1000
1001 #[test]
1002 fn test_form_validation_error() {
1003 let mut form = Form::new();
1004
1005 let mut name_field = CharField::new("name".to_string());
1006 name_field.max_length = Some(5);
1007 form.add_field(Box::new(name_field));
1008
1009 let mut data = HashMap::new();
1010 data.insert("name".to_string(), serde_json::json!("Very Long Name"));
1011
1012 form.bind(data);
1013 assert!(!form.is_valid());
1014 assert!(!form.errors().is_empty());
1015 }
1016
1017 #[test]
1020 fn test_form_basic() {
1021 use crate::fields::CharField;
1023
1024 let mut form = Form::new();
1025 form.add_field(Box::new(CharField::new("first_name".to_string())));
1026 form.add_field(Box::new(CharField::new("last_name".to_string())));
1027
1028 let mut data = HashMap::new();
1029 data.insert("first_name".to_string(), serde_json::json!("John"));
1030 data.insert("last_name".to_string(), serde_json::json!("Lennon"));
1031
1032 form.bind(data);
1033
1034 assert!(form.is_bound());
1035 assert!(form.is_valid());
1036 assert!(form.errors().is_empty());
1037
1038 let cleaned = form.cleaned_data();
1040 assert_eq!(
1041 cleaned.get("first_name").unwrap(),
1042 &serde_json::json!("John")
1043 );
1044 assert_eq!(
1045 cleaned.get("last_name").unwrap(),
1046 &serde_json::json!("Lennon")
1047 );
1048 }
1049
1050 #[test]
1051 fn test_form_missing_required_fields() {
1052 use crate::fields::CharField;
1054
1055 let mut form = Form::new();
1056 form.add_field(Box::new(CharField::new("username".to_string()).required()));
1057 form.add_field(Box::new(CharField::new("email".to_string()).required()));
1058
1059 let data = HashMap::new(); form.bind(data);
1062
1063 assert!(form.is_bound());
1064 assert!(!form.is_valid());
1065 assert!(form.errors().contains_key("username"));
1066 assert!(form.errors().contains_key("email"));
1067 }
1068
1069 #[test]
1070 fn test_form_optional_fields() {
1071 use crate::fields::CharField;
1073
1074 let mut form = Form::new();
1075
1076 let username_field = CharField::new("username".to_string());
1077 form.add_field(Box::new(username_field));
1078
1079 let mut bio_field = CharField::new("bio".to_string());
1080 bio_field.required = false;
1081 form.add_field(Box::new(bio_field));
1082
1083 let mut data = HashMap::new();
1084 data.insert("username".to_string(), serde_json::json!("john"));
1085 form.bind(data);
1088
1089 assert!(form.is_bound());
1090 assert!(form.is_valid());
1091 assert!(form.errors().is_empty());
1092 }
1093
1094 #[test]
1095 fn test_form_unbound() {
1096 use crate::fields::CharField;
1098
1099 let mut form = Form::new();
1100 form.add_field(Box::new(CharField::new("name".to_string())));
1101
1102 assert!(!form.is_bound());
1103 assert!(!form.is_valid()); }
1105
1106 #[test]
1107 fn test_form_extra_data() {
1108 use crate::fields::CharField;
1110
1111 let mut form = Form::new();
1112 form.add_field(Box::new(CharField::new("name".to_string())));
1113
1114 let mut data = HashMap::new();
1115 data.insert("name".to_string(), serde_json::json!("John"));
1116 data.insert(
1117 "extra_field".to_string(),
1118 serde_json::json!("should be ignored"),
1119 );
1120
1121 form.bind(data);
1122
1123 assert!(form.is_valid());
1124 let cleaned = form.cleaned_data();
1125 assert_eq!(cleaned.get("name").unwrap(), &serde_json::json!("John"));
1126 assert!(cleaned.contains_key("extra_field"));
1128 }
1129
1130 #[test]
1131 fn test_forms_form_multiple_fields() {
1132 use crate::fields::{CharField, IntegerField};
1134
1135 let mut form = Form::new();
1136 form.add_field(Box::new(CharField::new("username".to_string())));
1137
1138 let mut age_field = IntegerField::new("age".to_string());
1139 age_field.min_value = Some(0);
1140 age_field.max_value = Some(150);
1141 form.add_field(Box::new(age_field));
1142
1143 let mut data = HashMap::new();
1144 data.insert("username".to_string(), serde_json::json!("alice"));
1145 data.insert("age".to_string(), serde_json::json!(30));
1146
1147 form.bind(data);
1148
1149 assert!(form.is_valid());
1150 assert!(form.errors().is_empty());
1151 }
1152
1153 #[test]
1154 fn test_form_multiple_fields_invalid() {
1155 use crate::fields::{CharField, IntegerField};
1157
1158 let mut form = Form::new();
1159
1160 let mut username_field = CharField::new("username".to_string());
1161 username_field.min_length = Some(3);
1162 form.add_field(Box::new(username_field));
1163
1164 let mut age_field = IntegerField::new("age".to_string());
1165 age_field.min_value = Some(0);
1166 age_field.max_value = Some(150);
1167 form.add_field(Box::new(age_field));
1168
1169 let mut data = HashMap::new();
1170 data.insert("username".to_string(), serde_json::json!("ab")); data.insert("age".to_string(), serde_json::json!(200)); form.bind(data);
1174
1175 assert!(!form.is_valid());
1176 assert!(form.errors().contains_key("username"));
1177 assert!(form.errors().contains_key("age"));
1178 }
1179
1180 #[test]
1181 fn test_form_multiple_instances() {
1182 use crate::fields::CharField;
1184
1185 let mut form1 = Form::new();
1186 form1.add_field(Box::new(CharField::new("name".to_string())));
1187
1188 let mut form2 = Form::new();
1189 form2.add_field(Box::new(CharField::new("name".to_string())));
1190
1191 let mut data1 = HashMap::new();
1192 data1.insert("name".to_string(), serde_json::json!("Form1"));
1193 form1.bind(data1);
1194
1195 let mut data2 = HashMap::new();
1196 data2.insert("name".to_string(), serde_json::json!("Form2"));
1197 form2.bind(data2);
1198
1199 assert!(form1.is_valid());
1200 assert!(form2.is_valid());
1201
1202 assert_eq!(
1203 form1.cleaned_data().get("name").unwrap(),
1204 &serde_json::json!("Form1")
1205 );
1206 assert_eq!(
1207 form2.cleaned_data().get("name").unwrap(),
1208 &serde_json::json!("Form2")
1209 );
1210 }
1211
1212 #[test]
1213 fn test_form_with_initial_data() {
1214 let mut initial = HashMap::new();
1215 initial.insert("name".to_string(), serde_json::json!("Initial Name"));
1216 initial.insert("age".to_string(), serde_json::json!(25));
1217
1218 let mut form = Form::with_initial(initial);
1219
1220 let name_field = CharField::new("name".to_string());
1221 form.add_field(Box::new(name_field));
1222
1223 let age_field = crate::IntegerField::new("age".to_string());
1224 form.add_field(Box::new(age_field));
1225
1226 assert_eq!(
1227 form.initial().get("name").unwrap(),
1228 &serde_json::json!("Initial Name")
1229 );
1230 assert_eq!(form.initial().get("age").unwrap(), &serde_json::json!(25));
1231 }
1232
1233 #[test]
1234 fn test_form_has_changed() {
1235 let mut initial = HashMap::new();
1236 initial.insert("name".to_string(), serde_json::json!("John"));
1237
1238 let mut form = Form::with_initial(initial);
1239
1240 let name_field = CharField::new("name".to_string());
1241 form.add_field(Box::new(name_field));
1242
1243 let mut data1 = HashMap::new();
1245 data1.insert("name".to_string(), serde_json::json!("John"));
1246 form.bind(data1);
1247 assert!(!form.has_changed());
1248
1249 let mut data2 = HashMap::new();
1251 data2.insert("name".to_string(), serde_json::json!("Jane"));
1252 form.bind(data2);
1253 assert!(form.has_changed());
1254 }
1255
1256 #[test]
1257 fn test_form_index_access() {
1258 let mut form = Form::new();
1259
1260 let name_field = CharField::new("name".to_string());
1261 form.add_field(Box::new(name_field));
1262
1263 let field = &form["name"];
1264 assert_eq!(field.name(), "name");
1265 }
1266
1267 #[test]
1268 #[should_panic(expected = "Field 'nonexistent' not found")]
1269 fn test_form_index_access_nonexistent() {
1270 let form = Form::new();
1271 let _ = &form["nonexistent"];
1272 }
1273
1274 #[test]
1275 fn test_form_get_field() {
1276 let mut form = Form::new();
1277
1278 let name_field = CharField::new("name".to_string());
1279 form.add_field(Box::new(name_field));
1280
1281 assert!(form.get_field("name").is_some());
1282 assert!(form.get_field("nonexistent").is_none());
1283 }
1284
1285 #[test]
1286 fn test_form_remove_field() {
1287 let mut form = Form::new();
1288
1289 let name_field = CharField::new("name".to_string());
1290 form.add_field(Box::new(name_field));
1291
1292 assert_eq!(form.field_count(), 1);
1293
1294 let removed = form.remove_field("name");
1295 assert!(removed.is_some());
1296 assert_eq!(form.field_count(), 0);
1297
1298 let not_removed = form.remove_field("nonexistent");
1299 assert!(not_removed.is_none());
1300 }
1301
1302 #[test]
1303 fn test_form_custom_validation() {
1304 let mut form = Form::new();
1305
1306 let mut password_field = CharField::new("password".to_string());
1307 password_field.min_length = Some(8);
1308 form.add_field(Box::new(password_field));
1309
1310 let mut confirm_field = CharField::new("confirm".to_string());
1311 confirm_field.min_length = Some(8);
1312 form.add_field(Box::new(confirm_field));
1313
1314 form.add_clean_function(|data| {
1316 let password = data.get("password").and_then(|v| v.as_str());
1317 let confirm = data.get("confirm").and_then(|v| v.as_str());
1318
1319 if password != confirm {
1320 return Err(FormError::Validation("Passwords do not match".to_string()));
1321 }
1322
1323 Ok(())
1324 });
1325
1326 let mut data1 = HashMap::new();
1328 data1.insert("password".to_string(), serde_json::json!("secret123"));
1329 data1.insert("confirm".to_string(), serde_json::json!("secret123"));
1330 form.bind(data1);
1331 assert!(form.is_valid());
1332
1333 let mut data2 = HashMap::new();
1335 data2.insert("password".to_string(), serde_json::json!("secret123"));
1336 data2.insert("confirm".to_string(), serde_json::json!("different"));
1337 form.bind(data2);
1338 assert!(!form.is_valid());
1339 assert!(form.errors().contains_key(ALL_FIELDS_KEY));
1340 }
1341
1342 #[test]
1343 fn test_form_prefix() {
1344 let mut form = Form::with_prefix("profile".to_string());
1345 assert_eq!(form.prefix(), "profile");
1346 assert_eq!(form.add_prefix_to_field_name("name"), "profile-name");
1347
1348 form.set_prefix("user".to_string());
1349 assert_eq!(form.prefix(), "user");
1350 assert_eq!(form.add_prefix_to_field_name("email"), "user-email");
1351 }
1352
1353 #[test]
1354 fn test_form_field_clean_function() {
1355 let mut form = Form::new();
1356
1357 let mut name_field = CharField::new("name".to_string());
1358 name_field.required = true;
1359 form.add_field(Box::new(name_field));
1360
1361 form.add_field_clean_function("name", |value| {
1363 if let Some(s) = value.as_str() {
1364 Ok(serde_json::json!(s.to_uppercase()))
1365 } else {
1366 Err(FormError::Validation("Expected string".to_string()))
1367 }
1368 });
1369
1370 let mut data = HashMap::new();
1371 data.insert("name".to_string(), serde_json::json!("john doe"));
1372 form.bind(data);
1373
1374 assert!(form.is_valid());
1375 assert_eq!(
1376 form.cleaned_data().get("name").unwrap(),
1377 &serde_json::json!("JOHN DOE")
1378 );
1379 }
1380}