Skip to main content

reinhardt_forms/fields/
advanced_fields.rs

1//! Advanced field types for specialized data validation
2
3use crate::Widget;
4use crate::field::{FieldError, FieldResult, FormField};
5use serde_json::Value;
6use std::collections::HashMap;
7
8/// A field for UUID validation
9///
10/// Validates that the input is a valid UUID (Universally Unique Identifier).
11/// Supports UUID v4 by default.
12///
13/// # Examples
14///
15/// ```
16/// use reinhardt_forms::fields::UUIDField;
17/// use reinhardt_forms::Field;
18/// use serde_json::json;
19///
20/// let field = UUIDField::new("id");
21///
22/// // Valid UUID v4
23/// let result = field.clean(Some(&json!("550e8400-e29b-41d4-a716-446655440000")));
24/// assert!(result.is_ok());
25///
26/// // Invalid UUID
27/// let result = field.clean(Some(&json!("not-a-uuid")));
28/// assert!(result.is_err());
29/// ```
30#[derive(Debug, Clone)]
31pub struct UUIDField {
32	/// The field name used as the form data key.
33	pub name: String,
34	/// Whether this field must be filled in.
35	pub required: bool,
36	/// Custom error messages keyed by error type.
37	pub error_messages: HashMap<String, String>,
38	/// The widget type used for rendering this field.
39	pub widget: Widget,
40	/// Help text displayed alongside the field.
41	pub help_text: String,
42	/// Optional initial (default) value for the field.
43	pub initial: Option<Value>,
44}
45
46impl UUIDField {
47	/// Create a new UUIDField
48	pub fn new(name: impl Into<String>) -> Self {
49		let mut error_messages = HashMap::new();
50		error_messages.insert(
51			"required".to_string(),
52			"This field is required.".to_string(),
53		);
54		error_messages.insert("invalid".to_string(), "Enter a valid UUID.".to_string());
55
56		Self {
57			name: name.into(),
58			required: true,
59			error_messages,
60			widget: Widget::TextInput,
61			help_text: String::new(),
62			initial: None,
63		}
64	}
65
66	/// Set whether this field is required
67	pub fn required(mut self, required: bool) -> Self {
68		self.required = required;
69		self
70	}
71
72	/// Set the help text
73	pub fn help_text(mut self, text: impl Into<String>) -> Self {
74		self.help_text = text.into();
75		self
76	}
77
78	/// Set the initial value
79	pub fn initial(mut self, value: Value) -> Self {
80		self.initial = Some(value);
81		self
82	}
83
84	/// Set a custom error message
85	pub fn error_message(
86		mut self,
87		error_type: impl Into<String>,
88		message: impl Into<String>,
89	) -> Self {
90		self.error_messages
91			.insert(error_type.into(), message.into());
92		self
93	}
94
95	/// Validate UUID format
96	fn validate_uuid(&self, s: &str) -> bool {
97		// UUID format: 8-4-4-4-12 hexadecimal digits
98		let parts: Vec<&str> = s.split('-').collect();
99		if parts.len() != 5 {
100			return false;
101		}
102
103		if parts[0].len() != 8
104			|| parts[1].len() != 4
105			|| parts[2].len() != 4
106			|| parts[3].len() != 4
107			|| parts[4].len() != 12
108		{
109			return false;
110		}
111
112		parts
113			.iter()
114			.all(|part| part.chars().all(|c| c.is_ascii_hexdigit()))
115	}
116}
117
118impl FormField for UUIDField {
119	fn name(&self) -> &str {
120		&self.name
121	}
122
123	fn label(&self) -> Option<&str> {
124		None
125	}
126
127	fn widget(&self) -> &Widget {
128		&self.widget
129	}
130
131	fn required(&self) -> bool {
132		self.required
133	}
134
135	fn initial(&self) -> Option<&Value> {
136		self.initial.as_ref()
137	}
138
139	fn help_text(&self) -> Option<&str> {
140		if self.help_text.is_empty() {
141			None
142		} else {
143			Some(&self.help_text)
144		}
145	}
146
147	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
148		if value.is_none() || value == Some(&Value::Null) {
149			if self.required {
150				let error_msg = self
151					.error_messages
152					.get("required")
153					.cloned()
154					.unwrap_or_else(|| "This field is required.".to_string());
155				return Err(FieldError::validation(None, &error_msg));
156			}
157			return Ok(Value::Null);
158		}
159
160		let s = match value.unwrap() {
161			Value::String(s) => s.trim(),
162			_ => {
163				let error_msg = self
164					.error_messages
165					.get("invalid")
166					.cloned()
167					.unwrap_or_else(|| "Enter a valid UUID.".to_string());
168				return Err(FieldError::validation(None, &error_msg));
169			}
170		};
171
172		if s.is_empty() {
173			if self.required {
174				let error_msg = self
175					.error_messages
176					.get("required")
177					.cloned()
178					.unwrap_or_else(|| "This field is required.".to_string());
179				return Err(FieldError::validation(None, &error_msg));
180			}
181			return Ok(Value::Null);
182		}
183
184		if !self.validate_uuid(s) {
185			let error_msg = self
186				.error_messages
187				.get("invalid")
188				.cloned()
189				.unwrap_or_else(|| "Enter a valid UUID.".to_string());
190			return Err(FieldError::validation(None, &error_msg));
191		}
192
193		Ok(Value::String(s.to_lowercase()))
194	}
195
196	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
197		match (initial, data) {
198			(None, None) => false,
199			(Some(_), None) | (None, Some(_)) => true,
200			(Some(Value::String(a)), Some(Value::String(b))) => {
201				a.to_lowercase() != b.to_lowercase()
202			}
203			(Some(a), Some(b)) => a != b,
204		}
205	}
206}
207
208/// A field for ISO 8601 duration validation
209///
210/// Validates that the input is a valid ISO 8601 duration format (e.g., "P1Y2M3DT4H5M6S").
211///
212/// # Examples
213///
214/// ```
215/// use reinhardt_forms::fields::DurationField;
216/// use reinhardt_forms::Field;
217/// use serde_json::json;
218///
219/// let field = DurationField::new("duration");
220///
221/// // Valid duration
222/// let result = field.clean(Some(&json!("P1Y2M3DT4H5M6S")));
223/// assert!(result.is_ok());
224///
225/// // Another valid duration (1 day)
226/// let result = field.clean(Some(&json!("P1D")));
227/// assert!(result.is_ok());
228/// ```
229#[derive(Debug, Clone)]
230pub struct DurationField {
231	/// The field name used as the form data key.
232	pub name: String,
233	/// Whether this field must be filled in.
234	pub required: bool,
235	/// Custom error messages keyed by error type.
236	pub error_messages: HashMap<String, String>,
237	/// The widget type used for rendering this field.
238	pub widget: Widget,
239	/// Help text displayed alongside the field.
240	pub help_text: String,
241	/// Optional initial (default) value for the field.
242	pub initial: Option<Value>,
243}
244
245impl DurationField {
246	/// Create a new DurationField
247	pub fn new(name: impl Into<String>) -> Self {
248		let mut error_messages = HashMap::new();
249		error_messages.insert(
250			"required".to_string(),
251			"This field is required.".to_string(),
252		);
253		error_messages.insert(
254			"invalid".to_string(),
255			"Enter a valid ISO 8601 duration.".to_string(),
256		);
257
258		Self {
259			name: name.into(),
260			required: true,
261			error_messages,
262			widget: Widget::TextInput,
263			help_text: String::new(),
264			initial: None,
265		}
266	}
267
268	/// Set whether this field is required
269	pub fn required(mut self, required: bool) -> Self {
270		self.required = required;
271		self
272	}
273
274	/// Set the help text
275	pub fn help_text(mut self, text: impl Into<String>) -> Self {
276		self.help_text = text.into();
277		self
278	}
279
280	/// Set the initial value
281	pub fn initial(mut self, value: Value) -> Self {
282		self.initial = Some(value);
283		self
284	}
285
286	/// Set a custom error message
287	pub fn error_message(
288		mut self,
289		error_type: impl Into<String>,
290		message: impl Into<String>,
291	) -> Self {
292		self.error_messages
293			.insert(error_type.into(), message.into());
294		self
295	}
296
297	/// Validate ISO 8601 duration format
298	/// Format: P[n]Y[n]M[n]DT[n]H[n]M[n]S or P[n]W
299	fn validate_duration(&self, s: &str) -> bool {
300		if !s.starts_with('P') {
301			return false;
302		}
303
304		let s = &s[1..]; // Remove 'P' prefix
305
306		if s.is_empty() {
307			return false;
308		}
309
310		// Week format: P[n]W
311		if let Some(num_part) = s.strip_suffix('W') {
312			return num_part.chars().all(|c| c.is_ascii_digit());
313		}
314
315		// Date and time format
316		let parts: Vec<&str> = s.split('T').collect();
317
318		if parts.is_empty() || parts.len() > 2 {
319			return false;
320		}
321
322		// Validate date part (Y, M, D)
323		let date_valid = self.validate_duration_part(parts[0], &['Y', 'M', 'D']);
324
325		// Validate time part if present (H, M, S)
326		let time_valid = if parts.len() == 2 {
327			!parts[1].is_empty() && self.validate_duration_part(parts[1], &['H', 'M', 'S'])
328		} else {
329			true
330		};
331
332		date_valid && time_valid
333	}
334
335	/// Validate a duration part (either date or time)
336	fn validate_duration_part(&self, part: &str, units: &[char]) -> bool {
337		if part.is_empty() {
338			return true; // Empty parts are okay
339		}
340
341		let mut current_num = String::new();
342
343		for ch in part.chars() {
344			if ch.is_ascii_digit() || ch == '.' {
345				current_num.push(ch);
346			} else if units.contains(&ch) {
347				if current_num.is_empty() {
348					return false;
349				}
350				current_num.clear();
351			} else {
352				return false;
353			}
354		}
355
356		current_num.is_empty() // Should have consumed all digits
357	}
358}
359
360impl FormField for DurationField {
361	fn name(&self) -> &str {
362		&self.name
363	}
364
365	fn label(&self) -> Option<&str> {
366		None
367	}
368
369	fn widget(&self) -> &Widget {
370		&self.widget
371	}
372
373	fn required(&self) -> bool {
374		self.required
375	}
376
377	fn initial(&self) -> Option<&Value> {
378		self.initial.as_ref()
379	}
380
381	fn help_text(&self) -> Option<&str> {
382		if self.help_text.is_empty() {
383			None
384		} else {
385			Some(&self.help_text)
386		}
387	}
388
389	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
390		if value.is_none() || value == Some(&Value::Null) {
391			if self.required {
392				let error_msg = self
393					.error_messages
394					.get("required")
395					.cloned()
396					.unwrap_or_else(|| "This field is required.".to_string());
397				return Err(FieldError::validation(None, &error_msg));
398			}
399			return Ok(Value::Null);
400		}
401
402		let s = match value.unwrap() {
403			Value::String(s) => s.trim(),
404			_ => {
405				let error_msg = self
406					.error_messages
407					.get("invalid")
408					.cloned()
409					.unwrap_or_else(|| "Enter a valid ISO 8601 duration.".to_string());
410				return Err(FieldError::validation(None, &error_msg));
411			}
412		};
413
414		if s.is_empty() {
415			if self.required {
416				let error_msg = self
417					.error_messages
418					.get("required")
419					.cloned()
420					.unwrap_or_else(|| "This field is required.".to_string());
421				return Err(FieldError::validation(None, &error_msg));
422			}
423			return Ok(Value::Null);
424		}
425
426		if !self.validate_duration(s) {
427			let error_msg = self
428				.error_messages
429				.get("invalid")
430				.cloned()
431				.unwrap_or_else(|| "Enter a valid ISO 8601 duration.".to_string());
432			return Err(FieldError::validation(None, &error_msg));
433		}
434
435		Ok(Value::String(s.to_uppercase()))
436	}
437
438	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
439		match (initial, data) {
440			(None, None) => false,
441			(Some(_), None) | (None, Some(_)) => true,
442			(Some(Value::String(a)), Some(Value::String(b))) => {
443				a.to_uppercase() != b.to_uppercase()
444			}
445			(Some(a), Some(b)) => a != b,
446		}
447	}
448}
449
450/// A field that combines multiple field validators
451///
452/// ComboField runs all provided validators in sequence and requires all to pass.
453///
454/// # Examples
455///
456/// ```
457/// use reinhardt_forms::fields::ComboField;
458/// use reinhardt_forms::{Field, CharField, EmailField};
459/// use serde_json::json;
460///
461/// // Create validators with constraints
462/// let mut email_field = EmailField::new("email".to_string());
463/// let mut char_field = CharField::new("email".to_string());
464/// char_field.min_length = Some(5);
465/// char_field.max_length = Some(100);
466///
467/// // Combine email validation with length validation
468/// let field = ComboField::new("email")
469///     .add_validator(Box::new(email_field))
470///     .add_validator(Box::new(char_field));
471///
472/// // Valid: passes both email and length checks
473/// let result = field.clean(Some(&json!("user@example.com")));
474/// assert!(result.is_ok());
475///
476/// // Invalid: fails email validation
477/// let result = field.clean(Some(&json!("not-an-email")));
478/// assert!(result.is_err());
479///
480/// // Invalid: too short (less than 5 characters)
481/// let result = field.clean(Some(&json!("a@b")));
482/// assert!(result.is_err());
483/// ```
484pub struct ComboField {
485	/// The field name used as the form data key.
486	pub name: String,
487	/// Whether this field must be filled in.
488	pub required: bool,
489	/// Custom error messages keyed by error type.
490	pub error_messages: HashMap<String, String>,
491	/// The widget type used for rendering this field.
492	pub widget: Widget,
493	/// Help text displayed alongside the field.
494	pub help_text: String,
495	/// Optional initial (default) value for the field.
496	pub initial: Option<Value>,
497	/// The list of validator fields that all must pass.
498	pub validators: Vec<Box<dyn FormField>>,
499}
500
501impl ComboField {
502	/// Create a new ComboField
503	pub fn new(name: impl Into<String>) -> Self {
504		let mut error_messages = HashMap::new();
505		error_messages.insert(
506			"required".to_string(),
507			"This field is required.".to_string(),
508		);
509
510		Self {
511			name: name.into(),
512			required: true,
513			error_messages,
514			widget: Widget::TextInput,
515			help_text: String::new(),
516			initial: None,
517			validators: Vec::new(),
518		}
519	}
520
521	/// Add a validator field
522	pub fn add_validator(mut self, validator: Box<dyn FormField>) -> Self {
523		self.validators.push(validator);
524		self
525	}
526
527	/// Set whether this field is required
528	pub fn required(mut self, required: bool) -> Self {
529		self.required = required;
530		self
531	}
532
533	/// Set the help text
534	pub fn help_text(mut self, text: impl Into<String>) -> Self {
535		self.help_text = text.into();
536		self
537	}
538
539	/// Set the initial value
540	pub fn initial(mut self, value: Value) -> Self {
541		self.initial = Some(value);
542		self
543	}
544
545	/// Set a custom error message
546	pub fn error_message(
547		mut self,
548		error_type: impl Into<String>,
549		message: impl Into<String>,
550	) -> Self {
551		self.error_messages
552			.insert(error_type.into(), message.into());
553		self
554	}
555}
556
557impl FormField for ComboField {
558	fn name(&self) -> &str {
559		&self.name
560	}
561
562	fn label(&self) -> Option<&str> {
563		None
564	}
565
566	fn widget(&self) -> &Widget {
567		&self.widget
568	}
569
570	fn required(&self) -> bool {
571		self.required
572	}
573
574	fn initial(&self) -> Option<&Value> {
575		self.initial.as_ref()
576	}
577
578	fn help_text(&self) -> Option<&str> {
579		if self.help_text.is_empty() {
580			None
581		} else {
582			Some(&self.help_text)
583		}
584	}
585
586	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
587		if value.is_none() || value == Some(&Value::Null) {
588			if self.required {
589				let error_msg = self
590					.error_messages
591					.get("required")
592					.cloned()
593					.unwrap_or_else(|| "This field is required.".to_string());
594				return Err(FieldError::validation(None, &error_msg));
595			}
596			return Ok(Value::Null);
597		}
598
599		// Run all validators
600		let mut result = value.unwrap().clone();
601		for validator in &self.validators {
602			result = validator.clean(Some(&result))?;
603		}
604
605		Ok(result)
606	}
607
608	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
609		match (initial, data) {
610			(None, None) => false,
611			(Some(_), None) | (None, Some(_)) => true,
612			(Some(a), Some(b)) => a != b,
613		}
614	}
615}
616
617/// A field for color validation (hex format)
618///
619/// Validates that the input is a valid hex color code (e.g., "#FF0000" or "#F00").
620///
621/// # Examples
622///
623/// ```
624/// use reinhardt_forms::fields::ColorField;
625/// use reinhardt_forms::Field;
626/// use serde_json::json;
627///
628/// let field = ColorField::new("color");
629///
630/// // Valid 6-digit hex color
631/// let result = field.clean(Some(&json!("#FF0000")));
632/// assert!(result.is_ok());
633///
634/// // Valid 3-digit hex color
635/// let result = field.clean(Some(&json!("#F00")));
636/// assert!(result.is_ok());
637/// ```
638#[derive(Debug, Clone)]
639pub struct ColorField {
640	/// The field name used as the form data key.
641	pub name: String,
642	/// Whether this field must be filled in.
643	pub required: bool,
644	/// Custom error messages keyed by error type.
645	pub error_messages: HashMap<String, String>,
646	/// The widget type used for rendering this field.
647	pub widget: Widget,
648	/// Help text displayed alongside the field.
649	pub help_text: String,
650	/// Optional initial (default) value for the field.
651	pub initial: Option<Value>,
652}
653
654impl ColorField {
655	/// Create a new ColorField
656	pub fn new(name: impl Into<String>) -> Self {
657		let mut error_messages = HashMap::new();
658		error_messages.insert(
659			"required".to_string(),
660			"This field is required.".to_string(),
661		);
662		error_messages.insert(
663			"invalid".to_string(),
664			"Enter a valid hex color code (e.g., #FF0000).".to_string(),
665		);
666
667		Self {
668			name: name.into(),
669			required: true,
670			error_messages,
671			widget: Widget::TextInput,
672			help_text: String::new(),
673			initial: None,
674		}
675	}
676
677	/// Set whether this field is required
678	pub fn required(mut self, required: bool) -> Self {
679		self.required = required;
680		self
681	}
682
683	/// Set the help text
684	pub fn help_text(mut self, text: impl Into<String>) -> Self {
685		self.help_text = text.into();
686		self
687	}
688
689	/// Set the initial value
690	pub fn initial(mut self, value: Value) -> Self {
691		self.initial = Some(value);
692		self
693	}
694
695	/// Validate hex color format
696	fn validate_color(&self, s: &str) -> bool {
697		if !s.starts_with('#') {
698			return false;
699		}
700
701		let hex = &s[1..];
702		if hex.len() != 3 && hex.len() != 6 {
703			return false;
704		}
705
706		hex.chars().all(|c| c.is_ascii_hexdigit())
707	}
708}
709
710impl FormField for ColorField {
711	fn name(&self) -> &str {
712		&self.name
713	}
714
715	fn label(&self) -> Option<&str> {
716		None
717	}
718
719	fn widget(&self) -> &Widget {
720		&self.widget
721	}
722
723	fn required(&self) -> bool {
724		self.required
725	}
726
727	fn initial(&self) -> Option<&Value> {
728		self.initial.as_ref()
729	}
730
731	fn help_text(&self) -> Option<&str> {
732		if self.help_text.is_empty() {
733			None
734		} else {
735			Some(&self.help_text)
736		}
737	}
738
739	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
740		if value.is_none() || value == Some(&Value::Null) {
741			if self.required {
742				let error_msg = self
743					.error_messages
744					.get("required")
745					.cloned()
746					.unwrap_or_else(|| "This field is required.".to_string());
747				return Err(FieldError::validation(None, &error_msg));
748			}
749			return Ok(Value::Null);
750		}
751
752		let s = match value.unwrap() {
753			Value::String(s) => s.trim(),
754			_ => {
755				let error_msg = self
756					.error_messages
757					.get("invalid")
758					.cloned()
759					.unwrap_or_else(|| "Enter a valid hex color code.".to_string());
760				return Err(FieldError::validation(None, &error_msg));
761			}
762		};
763
764		if s.is_empty() {
765			if self.required {
766				let error_msg = self
767					.error_messages
768					.get("required")
769					.cloned()
770					.unwrap_or_else(|| "This field is required.".to_string());
771				return Err(FieldError::validation(None, &error_msg));
772			}
773			return Ok(Value::Null);
774		}
775
776		if !self.validate_color(s) {
777			let error_msg = self
778				.error_messages
779				.get("invalid")
780				.cloned()
781				.unwrap_or_else(|| "Enter a valid hex color code.".to_string());
782			return Err(FieldError::validation(None, &error_msg));
783		}
784
785		Ok(Value::String(s.to_uppercase()))
786	}
787
788	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
789		match (initial, data) {
790			(None, None) => false,
791			(Some(_), None) | (None, Some(_)) => true,
792			(Some(Value::String(a)), Some(Value::String(b))) => {
793				a.to_uppercase() != b.to_uppercase()
794			}
795			(Some(a), Some(b)) => a != b,
796		}
797	}
798}
799
800/// Redacted placeholder value stored in cleaned data for password fields.
801///
802/// Password fields store this constant instead of the plaintext password
803/// to prevent accidental exposure of credentials in cleaned form data.
804pub const PASSWORD_REDACTED: &str = "**********";
805
806/// A field for password validation with strength requirements
807///
808/// Validates password strength including minimum length, required character types.
809///
810/// # Examples
811///
812/// ```
813/// use reinhardt_forms::fields::PasswordField;
814/// use reinhardt_forms::Field;
815/// use serde_json::json;
816///
817/// let field = PasswordField::new("password")
818///     .min_length(8)
819///     .require_uppercase(true)
820///     .require_digit(true);
821///
822/// // Valid strong password
823/// let result = field.clean(Some(&json!("SecurePass123")));
824/// assert!(result.is_ok());
825///
826/// // Invalid: too short
827/// let result = field.clean(Some(&json!("Pass1")));
828/// assert!(result.is_err());
829/// ```
830#[derive(Debug, Clone)]
831pub struct PasswordField {
832	/// The field name used as the form data key.
833	pub name: String,
834	/// Whether this field must be filled in.
835	pub required: bool,
836	/// Custom error messages keyed by error type.
837	pub error_messages: HashMap<String, String>,
838	/// The widget type used for rendering this field.
839	pub widget: Widget,
840	/// Help text displayed alongside the field.
841	pub help_text: String,
842	/// Optional initial (default) value for the field.
843	pub initial: Option<Value>,
844	/// Minimum required password length.
845	pub min_length: usize,
846	/// Whether the password must contain at least one uppercase letter.
847	pub require_uppercase: bool,
848	/// Whether the password must contain at least one lowercase letter.
849	pub require_lowercase: bool,
850	/// Whether the password must contain at least one digit.
851	pub require_digit: bool,
852	/// Whether the password must contain at least one special character.
853	pub require_special: bool,
854}
855
856impl PasswordField {
857	/// Create a new PasswordField
858	pub fn new(name: impl Into<String>) -> Self {
859		let mut error_messages = HashMap::new();
860		error_messages.insert(
861			"required".to_string(),
862			"This field is required.".to_string(),
863		);
864		error_messages.insert(
865			"too_short".to_string(),
866			"Password must be at least {min_length} characters.".to_string(),
867		);
868		error_messages.insert(
869			"no_uppercase".to_string(),
870			"Password must contain at least one uppercase letter.".to_string(),
871		);
872		error_messages.insert(
873			"no_lowercase".to_string(),
874			"Password must contain at least one lowercase letter.".to_string(),
875		);
876		error_messages.insert(
877			"no_digit".to_string(),
878			"Password must contain at least one digit.".to_string(),
879		);
880		error_messages.insert(
881			"no_special".to_string(),
882			"Password must contain at least one special character.".to_string(),
883		);
884
885		Self {
886			name: name.into(),
887			required: true,
888			error_messages,
889			widget: Widget::PasswordInput,
890			help_text: String::new(),
891			initial: None,
892			min_length: 8,
893			require_uppercase: false,
894			require_lowercase: false,
895			require_digit: false,
896			require_special: false,
897		}
898	}
899
900	/// Set minimum length
901	pub fn min_length(mut self, length: usize) -> Self {
902		self.min_length = length;
903		self
904	}
905
906	/// Require uppercase letter
907	pub fn require_uppercase(mut self, required: bool) -> Self {
908		self.require_uppercase = required;
909		self
910	}
911
912	/// Require lowercase letter
913	pub fn require_lowercase(mut self, required: bool) -> Self {
914		self.require_lowercase = required;
915		self
916	}
917
918	/// Require digit
919	pub fn require_digit(mut self, required: bool) -> Self {
920		self.require_digit = required;
921		self
922	}
923
924	/// Require special character
925	pub fn require_special(mut self, required: bool) -> Self {
926		self.require_special = required;
927		self
928	}
929
930	/// Set whether this field is required
931	pub fn required(mut self, required: bool) -> Self {
932		self.required = required;
933		self
934	}
935
936	/// Set the help text
937	pub fn help_text(mut self, text: impl Into<String>) -> Self {
938		self.help_text = text.into();
939		self
940	}
941}
942
943impl FormField for PasswordField {
944	fn name(&self) -> &str {
945		&self.name
946	}
947
948	fn label(&self) -> Option<&str> {
949		None
950	}
951
952	fn widget(&self) -> &Widget {
953		&self.widget
954	}
955
956	fn required(&self) -> bool {
957		self.required
958	}
959
960	fn initial(&self) -> Option<&Value> {
961		self.initial.as_ref()
962	}
963
964	fn help_text(&self) -> Option<&str> {
965		if self.help_text.is_empty() {
966			None
967		} else {
968			Some(&self.help_text)
969		}
970	}
971
972	fn clean(&self, value: Option<&Value>) -> FieldResult<Value> {
973		if value.is_none() || value == Some(&Value::Null) {
974			if self.required {
975				let error_msg = self
976					.error_messages
977					.get("required")
978					.cloned()
979					.unwrap_or_else(|| "This field is required.".to_string());
980				return Err(FieldError::validation(None, &error_msg));
981			}
982			return Ok(Value::Null);
983		}
984
985		let s = match value.unwrap() {
986			Value::String(s) => s,
987			_ => {
988				return Err(FieldError::validation(
989					Some(&self.name),
990					"Invalid password format.",
991				));
992			}
993		};
994
995		if s.is_empty() {
996			if self.required {
997				let error_msg = self
998					.error_messages
999					.get("required")
1000					.cloned()
1001					.unwrap_or_else(|| "This field is required.".to_string());
1002				return Err(FieldError::validation(None, &error_msg));
1003			}
1004			return Ok(Value::Null);
1005		}
1006
1007		// Check minimum length
1008		if s.len() < self.min_length {
1009			let error_msg = self
1010				.error_messages
1011				.get("too_short")
1012				.cloned()
1013				.unwrap_or_else(|| {
1014					format!("Password must be at least {} characters.", self.min_length)
1015				});
1016			return Err(FieldError::validation(None, &error_msg));
1017		}
1018
1019		// Check uppercase requirement
1020		if self.require_uppercase && !s.chars().any(|c| c.is_uppercase()) {
1021			let error_msg = self
1022				.error_messages
1023				.get("no_uppercase")
1024				.cloned()
1025				.unwrap_or_else(|| {
1026					"Password must contain at least one uppercase letter.".to_string()
1027				});
1028			return Err(FieldError::validation(None, &error_msg));
1029		}
1030
1031		// Check lowercase requirement
1032		if self.require_lowercase && !s.chars().any(|c| c.is_lowercase()) {
1033			let error_msg = self
1034				.error_messages
1035				.get("no_lowercase")
1036				.cloned()
1037				.unwrap_or_else(|| {
1038					"Password must contain at least one lowercase letter.".to_string()
1039				});
1040			return Err(FieldError::validation(None, &error_msg));
1041		}
1042
1043		// Check digit requirement
1044		if self.require_digit && !s.chars().any(|c| c.is_ascii_digit()) {
1045			let error_msg = self
1046				.error_messages
1047				.get("no_digit")
1048				.cloned()
1049				.unwrap_or_else(|| "Password must contain at least one digit.".to_string());
1050			return Err(FieldError::validation(None, &error_msg));
1051		}
1052
1053		// Check special character requirement
1054		if self.require_special && !s.chars().any(|c| !c.is_alphanumeric()) {
1055			let error_msg = self
1056				.error_messages
1057				.get("no_special")
1058				.cloned()
1059				.unwrap_or_else(|| {
1060					"Password must contain at least one special character.".to_string()
1061				});
1062			return Err(FieldError::validation(None, &error_msg));
1063		}
1064
1065		// Redact password to prevent plaintext storage in cleaned data
1066		Ok(Value::String(PASSWORD_REDACTED.to_string()))
1067	}
1068
1069	fn has_changed(&self, initial: Option<&Value>, data: Option<&Value>) -> bool {
1070		match (initial, data) {
1071			(None, None) => false,
1072			(Some(_), None) | (None, Some(_)) => true,
1073			(Some(a), Some(b)) => a != b,
1074		}
1075	}
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080	use super::*;
1081	use serde_json::json;
1082
1083	#[test]
1084	fn test_uuid_field_valid() {
1085		let field = UUIDField::new("id");
1086
1087		let result = field.clean(Some(&json!("550e8400-e29b-41d4-a716-446655440000")));
1088		assert!(result.is_ok());
1089
1090		// Case insensitive
1091		let result = field.clean(Some(&json!("550E8400-E29B-41D4-A716-446655440000")));
1092		assert!(result.is_ok());
1093	}
1094
1095	#[test]
1096	fn test_uuid_field_invalid() {
1097		let field = UUIDField::new("id");
1098
1099		// Too short
1100		let result = field.clean(Some(&json!("550e8400-e29b")));
1101		assert!(result.is_err());
1102
1103		// Invalid characters
1104		let result = field.clean(Some(&json!("550e8400-e29b-41d4-a716-44665544000g")));
1105		assert!(result.is_err());
1106
1107		// Wrong format
1108		let result = field.clean(Some(&json!("not-a-uuid")));
1109		assert!(result.is_err());
1110	}
1111
1112	#[test]
1113	fn test_duration_field_valid() {
1114		let field = DurationField::new("duration");
1115
1116		// Full format
1117		let result = field.clean(Some(&json!("P1Y2M3DT4H5M6S")));
1118		assert!(result.is_ok());
1119
1120		// Days only
1121		let result = field.clean(Some(&json!("P1D")));
1122		assert!(result.is_ok());
1123
1124		// Time only
1125		let result = field.clean(Some(&json!("PT1H")));
1126		assert!(result.is_ok());
1127
1128		// Weeks
1129		let result = field.clean(Some(&json!("P2W")));
1130		assert!(result.is_ok());
1131	}
1132
1133	#[test]
1134	fn test_duration_field_invalid() {
1135		let field = DurationField::new("duration");
1136
1137		// Missing P prefix
1138		let result = field.clean(Some(&json!("1Y2M")));
1139		assert!(result.is_err());
1140
1141		// Empty after P
1142		let result = field.clean(Some(&json!("P")));
1143		assert!(result.is_err());
1144
1145		// Invalid format
1146		let result = field.clean(Some(&json!("P1X")));
1147		assert!(result.is_err());
1148	}
1149
1150	#[test]
1151	fn test_combo_field() {
1152		use crate::EmailField;
1153
1154		// Create a validator with length and email constraints
1155		let mut char_field_min = crate::CharField::new("text".to_string());
1156		char_field_min.min_length = Some(5);
1157
1158		let mut char_field_max = crate::CharField::new("text".to_string());
1159		char_field_max.max_length = Some(50);
1160
1161		let field = ComboField::new("email")
1162			.add_validator(Box::new(char_field_min))
1163			.add_validator(Box::new(char_field_max))
1164			.add_validator(Box::new(EmailField::new("email".to_string())));
1165
1166		// Valid
1167		let result = field.clean(Some(&json!("test@example.com")));
1168		assert!(result.is_ok());
1169
1170		// Too short
1171		let result = field.clean(Some(&json!("a@b")));
1172		assert!(result.is_err());
1173
1174		// Not an email
1175		let result = field.clean(Some(&json!("hello world")));
1176		assert!(result.is_err());
1177	}
1178
1179	#[test]
1180	fn test_color_field_valid() {
1181		let field = ColorField::new("color");
1182
1183		// 6-digit hex
1184		let result = field.clean(Some(&json!("#FF0000")));
1185		assert!(result.is_ok());
1186
1187		// 3-digit hex
1188		let result = field.clean(Some(&json!("#F00")));
1189		assert!(result.is_ok());
1190
1191		// Lowercase
1192		let result = field.clean(Some(&json!("#ff0000")));
1193		assert!(result.is_ok());
1194	}
1195
1196	#[test]
1197	fn test_color_field_invalid() {
1198		let field = ColorField::new("color");
1199
1200		// Missing #
1201		let result = field.clean(Some(&json!("FF0000")));
1202		assert!(result.is_err());
1203
1204		// Invalid length
1205		let result = field.clean(Some(&json!("#FF00")));
1206		assert!(result.is_err());
1207
1208		// Invalid characters
1209		let result = field.clean(Some(&json!("#GGGGGG")));
1210		assert!(result.is_err());
1211	}
1212
1213	#[test]
1214	fn test_password_field_basic() {
1215		let field = PasswordField::new("password").min_length(6);
1216
1217		// Valid
1218		let result = field.clean(Some(&json!("password123")));
1219		assert!(result.is_ok());
1220
1221		// Too short
1222		let result = field.clean(Some(&json!("pass")));
1223		assert!(result.is_err());
1224	}
1225
1226	#[test]
1227	fn test_password_field_requirements() {
1228		let field = PasswordField::new("password")
1229			.min_length(8)
1230			.require_uppercase(true)
1231			.require_digit(true)
1232			.require_special(true);
1233
1234		// Valid: has uppercase, digit, and special char
1235		let result = field.clean(Some(&json!("SecurePass123!")));
1236		assert!(result.is_ok());
1237
1238		// Missing uppercase
1239		let result = field.clean(Some(&json!("password123!")));
1240		assert!(result.is_err());
1241
1242		// Missing digit
1243		let result = field.clean(Some(&json!("SecurePassword!")));
1244		assert!(result.is_err());
1245
1246		// Missing special char
1247		let result = field.clean(Some(&json!("SecurePassword123")));
1248		assert!(result.is_err());
1249	}
1250}