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