From 3036946750fcb7f16c2e1ed5c74330927807d4ae Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 29 Apr 2025 11:25:53 +0000 Subject: [PATCH 1/4] Move OptionField validation to Option Initialization --- cot/src/form/fields.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/cot/src/form/fields.rs b/cot/src/form/fields.rs index e55ab5ba..f310e7f1 100644 --- a/cot/src/form/fields.rs +++ b/cot/src/form/fields.rs @@ -32,8 +32,14 @@ macro_rules! impl_form_field { fn with_options( options: FormFieldOptions, custom_options: Self::CustomOptions, - ) -> Self { - Self { + ) -> Self + where Self: Sized + { + custom_options.validate_options() + .unwrap_or_else(|err| { + panic!("Could not initialize {}: {}", stringify!($field_options_type_name), err) + }); + Self { options, custom_options, value: None, @@ -55,6 +61,12 @@ macro_rules! impl_form_field { }; } +pub trait ValidateOptions { + fn validate_options(&self) -> Result<(), FormFieldValidationError> { + Ok(()) + } +} + impl_form_field!(StringField, StringFieldOptions, "a string"); /// Custom options for a [`StringField`]. @@ -84,6 +96,8 @@ impl Display for StringField { } } +impl ValidateOptions for StringFieldOptions {} + impl HtmlSafe for StringField {} impl AsFormField for String { @@ -153,6 +167,7 @@ impl Display for PasswordField { } } +impl ValidateOptions for PasswordFieldOptions {} impl HtmlSafe for PasswordField {} impl AsFormField for Password { @@ -273,6 +288,20 @@ impl AsFormField for Email { } } +impl ValidateOptions for EmailFieldOptions { + fn validate_options(&self) -> Result<(), FormFieldValidationError> { + if let (Some(min), Some(max)) = (self.min_length, self.max_length) { + if min > max { + return Err(FormFieldValidationError::from_string(format!( + "min_length ({}) exceeds max_length ({})", + min, max + ))); + } + } + Ok(()) + } +} + impl HtmlSafe for EmailField {} impl_form_field!(IntegerField, IntegerFieldOptions, "an integer", T: Integer); @@ -319,6 +348,8 @@ impl Display for IntegerField { } } +impl ValidateOptions for IntegerFieldOptions {} + impl HtmlSafe for IntegerField {} /// A trait for numerical types that optionally have minimum and maximum values. @@ -472,6 +503,7 @@ impl Display for BoolField { } } +impl ValidateOptions for BoolFieldOptions {} impl HtmlSafe for BoolField {} /// Implementation of `AsFormField` for `bool`. From 8f7f34bbe681153729896dccdf804568d4c6495e Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 29 Apr 2025 12:47:39 +0000 Subject: [PATCH 2/4] add a validation test for email --- cot/src/form/fields.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cot/src/form/fields.rs b/cot/src/form/fields.rs index f310e7f1..3de5e64b 100644 --- a/cot/src/form/fields.rs +++ b/cot/src/form/fields.rs @@ -932,4 +932,24 @@ mod tests { let value = bool::clean_value(&field).unwrap(); assert!(value); } + + #[test] + #[should_panic( + expected = "Could not initialize EmailFieldOptions: min_length (50) exceeds max_length (10)" + )] + fn email_field_validate_options() { + let bad_opts = EmailFieldOptions { + min_length: Some(50), + max_length: Some(10), + }; + + let _ = EmailField::with_options( + FormFieldOptions { + id: "foo".into(), + name: "foo".into(), + required: true, + }, + bad_opts, + ); + } } From ed7a058f8ced5c4805af0e3f19cefdab0900b572 Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 29 Apr 2025 12:54:34 +0000 Subject: [PATCH 3/4] remove dead test --- cot/src/form/fields.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/cot/src/form/fields.rs b/cot/src/form/fields.rs index 3de5e64b..9d63172a 100644 --- a/cot/src/form/fields.rs +++ b/cot/src/form/fields.rs @@ -874,30 +874,6 @@ mod tests { )); } - #[test] - fn email_field_clean_invalid_length_options() { - let mut field = EmailField::with_options( - FormFieldOptions { - id: "email_test".to_owned(), - name: "email_test".to_owned(), - required: true, - }, - EmailFieldOptions { - min_length: Some(50), - max_length: Some(10), - }, - ); - - field.set_value(Cow::Borrowed("user@example.com")); - let result = Email::clean_value(&field); - - assert!(result.is_err()); - if let Err(err) = result { - let msg = err.to_string(); - assert!(msg.contains("min_length") && msg.contains("exceeds max_length")); - } - } - #[test] fn integer_field_clean_value() { let mut field = IntegerField::::with_options( From fd69bffb721846034154e53c3d41ace3a06d1047 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 30 Apr 2025 11:21:48 +0000 Subject: [PATCH 4/4] add some docs to please clippy --- cot/src/form/fields.rs | 49 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/cot/src/form/fields.rs b/cot/src/form/fields.rs index 9c2e02f6..3f4ca3ab 100644 --- a/cot/src/form/fields.rs +++ b/cot/src/form/fields.rs @@ -61,7 +61,53 @@ macro_rules! impl_form_field { }; } +/// A trait for validating form field options. +/// +/// This trait is used to validate the custom options for form fields when they +/// are created. It ensures that the options provided are valid before a form +/// field is constructed. +/// +/// Implementing this trait allows for custom validation logic to be applied to +/// field options. +/// +/// # Examples +/// +/// ``` +/// use cot::form::fields::ValidateOptions; +/// use cot::form::{FormFieldOptions, FormFieldValidationError}; +/// +/// struct MyFieldOptions { +/// min: Option, +/// max: Option, +/// } +/// +/// impl ValidateOptions for MyFieldOptions { +/// fn validate_options(&self) -> Result<(), FormFieldValidationError> { +/// if let (Some(min), Some(max)) = (self.min, self.max) { +/// if min > max { +/// return Err(FormFieldValidationError::from_string(format!( +/// "min ({min}) cannot be greater than max ({max})" +/// ))); +/// } +/// } +/// Ok(()) +/// } +/// } +/// ``` pub trait ValidateOptions { + /// Validates the form field options when a field is created. + /// + /// This method is used to check that the provided options are valid and + /// consistent before a form field is constructed. The validation is + /// performed when `FormField::with_options` is called. + /// + /// The default implementation performs no validation and always returns + /// `Ok(())`. + /// + /// # Errors + /// + /// Returns an error if the options are invalid or inconsistent with each + /// other. fn validate_options(&self) -> Result<(), FormFieldValidationError> { Ok(()) } @@ -293,8 +339,7 @@ impl ValidateOptions for EmailFieldOptions { if let (Some(min), Some(max)) = (self.min_length, self.max_length) { if min > max { return Err(FormFieldValidationError::from_string(format!( - "min_length ({}) exceeds max_length ({})", - min, max + "min_length ({min}) exceeds max_length ({max})" ))); } }