cot/form/
fields.rs

1mod attrs;
2mod chrono;
3mod files;
4mod select;
5
6use std::fmt::{Debug, Display, Formatter};
7use std::num::{
8    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
9    NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
10};
11
12use askama::filters::HtmlSafe;
13pub use attrs::Step;
14pub use chrono::{
15    DateField, DateFieldOptions, DateTimeField, DateTimeFieldOptions, DateTimeWithTimezoneField,
16    DateTimeWithTimezoneFieldOptions, TimeField, TimeFieldOptions,
17};
18pub use files::{FileField, FileFieldOptions, InMemoryUploadedFile};
19pub(crate) use select::check_required_multiple;
20pub use select::{
21    SelectAsFormField, SelectChoice, SelectField, SelectFieldOptions, SelectMultipleField,
22    SelectMultipleFieldOptions,
23};
24
25use crate::auth::PasswordHash;
26use crate::common_types::{Email, Password, Url};
27#[cfg(feature = "db")]
28use crate::db::{Auto, ForeignKey, LimitedString, Model};
29use crate::form::{AsFormField, FormField, FormFieldOptions, FormFieldValidationError};
30use crate::html::HtmlTag;
31
32macro_rules! impl_form_field {
33    ($field_type_name:ident, $field_options_type_name:ident, $purpose:literal $(, $generic_param:ident $(: $generic_param_bound:ident $(+ $generic_param_bound_more:ident)*)?)?) => {
34        #[derive(Debug)]
35        #[doc = concat!("A form field for ", $purpose, ".")]
36        pub struct $field_type_name $(<$generic_param>)? {
37            options: $crate::form::FormFieldOptions,
38            custom_options: $field_options_type_name $(<$generic_param>)?,
39            value: Option<String>,
40        }
41
42        impl $(<$generic_param $(: $generic_param_bound $(+ $generic_param_bound_more)* )?>)? $crate::form::FormField for $field_type_name $(<$generic_param>)? {
43            type CustomOptions = $field_options_type_name $(<$generic_param>)?;
44
45            fn with_options(
46                options: $crate::form::FormFieldOptions,
47                custom_options: Self::CustomOptions,
48            ) -> Self {
49                Self {
50                    options,
51                    custom_options,
52                    value: None,
53                }
54            }
55
56            fn options(&self) -> &$crate::form::FormFieldOptions {
57                &self.options
58            }
59
60            fn value(&self) -> Option<&str> {
61                self.value.as_deref()
62            }
63
64            async fn set_value(&mut self, field: $crate::form::FormFieldValue<'_>) -> std::result::Result<(), $crate::form::FormFieldValueError> {
65                self.value = Some(field.into_text().await?);
66                Ok(())
67            }
68        }
69    };
70}
71pub(crate) use impl_form_field;
72
73impl_form_field!(StringField, StringFieldOptions, "a string");
74
75/// Custom options for a [`StringField`].
76#[derive(Debug, Default, Copy, Clone)]
77pub struct StringFieldOptions {
78    /// The maximum length of the field. Used to set the `maxlength` attribute
79    /// in the HTML input element.
80    pub max_length: Option<u32>,
81}
82
83impl Display for StringField {
84    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85        let mut tag = HtmlTag::input("text");
86        tag.attr("name", self.id());
87        tag.attr("id", self.id());
88        if self.options.required {
89            tag.bool_attr("required");
90        }
91        if let Some(max_length) = self.custom_options.max_length {
92            tag.attr("maxlength", max_length.to_string());
93        }
94        if let Some(value) = &self.value {
95            tag.attr("value", value);
96        }
97
98        write!(f, "{}", tag.render())
99    }
100}
101
102impl HtmlSafe for StringField {}
103
104impl AsFormField for String {
105    type Type = StringField;
106
107    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
108        let value = check_required(field)?;
109
110        if let Some(max_length) = field.custom_options.max_length
111            && value.len() > max_length as usize
112        {
113            return Err(FormFieldValidationError::maximum_length_exceeded(
114                max_length,
115            ));
116        }
117        Ok(value.to_owned())
118    }
119
120    fn to_field_value(&self) -> String {
121        self.to_owned()
122    }
123}
124
125#[cfg(feature = "db")]
126impl<const LEN: u32> AsFormField for LimitedString<LEN> {
127    type Type = StringField;
128
129    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
130        let value = check_required(field)?;
131
132        if value.len() > LEN as usize {
133            return Err(FormFieldValidationError::maximum_length_exceeded(LEN));
134        }
135        Ok(LimitedString::new(value.to_owned()).expect("length has already been checked"))
136    }
137
138    fn to_field_value(&self) -> String {
139        self.to_string()
140    }
141}
142
143impl_form_field!(PasswordField, PasswordFieldOptions, "a password");
144
145/// Custom options for a [`PasswordField`].
146#[derive(Debug, Default, Copy, Clone)]
147pub struct PasswordFieldOptions {
148    /// The maximum length of the field. Used to set the `maxlength` attribute
149    /// in the HTML input element.
150    pub max_length: Option<u32>,
151}
152
153impl Display for PasswordField {
154    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
155        let mut tag = HtmlTag::input("password");
156        tag.attr("name", self.id());
157        tag.attr("id", self.id());
158        if self.options.required {
159            tag.bool_attr("required");
160        }
161        if let Some(max_length) = self.custom_options.max_length {
162            tag.attr("maxlength", max_length.to_string());
163        }
164        // we don't set the value attribute for password fields
165        // to avoid leaking the password in the HTML
166
167        write!(f, "{}", tag.render())
168    }
169}
170
171impl HtmlSafe for PasswordField {}
172
173impl AsFormField for Password {
174    type Type = PasswordField;
175
176    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
177        let value = check_required(field)?;
178
179        if let Some(max_length) = field.custom_options.max_length
180            && value.len() > max_length as usize
181        {
182            return Err(FormFieldValidationError::maximum_length_exceeded(
183                max_length,
184            ));
185        }
186
187        Ok(Password::new(value))
188    }
189
190    fn to_field_value(&self) -> String {
191        self.as_str().to_owned()
192    }
193}
194
195impl AsFormField for PasswordHash {
196    type Type = PasswordField;
197
198    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
199        let value = check_required(field)?;
200
201        if let Some(max_length) = field.custom_options.max_length
202            && value.len() > max_length as usize
203        {
204            return Err(FormFieldValidationError::maximum_length_exceeded(
205                max_length,
206            ));
207        }
208
209        Ok(PasswordHash::from_password(&Password::new(value)))
210    }
211
212    fn to_field_value(&self) -> String {
213        // cannot return the original password
214        String::new()
215    }
216}
217
218impl_form_field!(EmailField, EmailFieldOptions, "an email");
219
220/// Custom options for [`EmailField`]
221#[derive(Debug, Default, Copy, Clone)]
222pub struct EmailFieldOptions {
223    /// The maximum length of the field used to set the `maxlength` attribute
224    /// in the HTML input element.
225    pub max_length: Option<u32>,
226    /// The minimum length of the field used to set the `minlength` attribute
227    /// in the HTML input element.
228    pub min_length: Option<u32>,
229}
230
231impl Display for EmailField {
232    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
233        let mut tag = HtmlTag::input("email");
234        tag.attr("name", self.id());
235        tag.attr("id", self.id());
236        if self.options.required {
237            tag.bool_attr("required");
238        }
239        if let Some(max_length) = self.custom_options.max_length {
240            tag.attr("maxlength", max_length.to_string());
241        }
242        if let Some(min_length) = self.custom_options.min_length {
243            tag.attr("minlength", min_length.to_string());
244        }
245        if let Some(value) = &self.value {
246            tag.attr("value", value);
247        }
248
249        write!(f, "{}", tag.render())
250    }
251}
252
253impl AsFormField for Email {
254    type Type = EmailField;
255
256    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError>
257    where
258        Self: Sized,
259    {
260        let value = check_required(field)?;
261        let opts = &field.custom_options;
262
263        if let (Some(min), Some(max)) = (opts.min_length, opts.max_length)
264            && min > max
265        {
266            return Err(FormFieldValidationError::from_string(format!(
267                "min_length ({min}) exceeds max_length ({max})"
268            )));
269        }
270
271        if let Some(min) = opts.min_length
272            && value.len() < min as usize
273        {
274            return Err(FormFieldValidationError::minimum_length_not_met(min));
275        }
276
277        if let Some(max) = opts.max_length
278            && value.len() > max as usize
279        {
280            return Err(FormFieldValidationError::maximum_length_exceeded(max));
281        }
282
283        Ok(value.parse()?)
284    }
285
286    fn to_field_value(&self) -> String {
287        self.as_str().to_owned()
288    }
289}
290
291impl HtmlSafe for EmailField {}
292
293impl_form_field!(IntegerField, IntegerFieldOptions, "an integer", T: Integer);
294
295/// Custom options for a [`IntegerField`].
296#[derive(Debug, Copy, Clone)]
297pub struct IntegerFieldOptions<T> {
298    /// The minimum value of the field. Used to set the `min` attribute in the
299    /// HTML input element.
300    pub min: Option<T>,
301    /// The maximum value of the field. Used to set the `max` attribute in the
302    /// HTML input element.
303    pub max: Option<T>,
304}
305
306impl<T: Integer> Default for IntegerFieldOptions<T> {
307    fn default() -> Self {
308        Self {
309            min: T::MIN,
310            max: T::MAX,
311        }
312    }
313}
314
315impl<T: Integer> Display for IntegerField<T> {
316    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
317        let mut tag = HtmlTag::input("number");
318        tag.attr("name", self.id());
319        tag.attr("id", self.id());
320        if self.options.required {
321            tag.bool_attr("required");
322        }
323        if let Some(min) = &self.custom_options.min {
324            tag.attr("min", min.to_string());
325        }
326        if let Some(max) = &self.custom_options.max {
327            tag.attr("max", max.to_string());
328        }
329        if let Some(value) = &self.value {
330            tag.attr("value", value);
331        }
332
333        write!(f, "{}", tag.render())
334    }
335}
336
337impl<T: Integer> HtmlSafe for IntegerField<T> {}
338
339/// A trait for numerical types that optionally have minimum and maximum values.
340///
341/// # Examples
342///
343/// ```
344/// use cot::form::fields::Integer;
345///
346/// assert_eq!(<i8 as Integer>::MIN, Some(-128));
347/// assert_eq!(<i8 as Integer>::MAX, Some(127));
348/// ```
349pub trait Integer: Sized + ToString + Send {
350    /// The minimum value of the type.
351    ///
352    /// # Examples
353    ///
354    /// ```
355    /// use cot::form::fields::Integer;
356    ///
357    /// assert_eq!(<i8 as Integer>::MIN, Some(-128));
358    /// ```
359    const MIN: Option<Self>;
360    /// The maximum value of the type.
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// use cot::form::fields::Integer;
366    ///
367    /// assert_eq!(<i8 as Integer>::MAX, Some(127));
368    /// ```
369    const MAX: Option<Self>;
370}
371
372macro_rules! impl_integer {
373    ($type:ty) => {
374        impl Integer for $type {
375            const MAX: Option<Self> = Some(Self::MAX);
376            const MIN: Option<Self> = Some(Self::MIN);
377        }
378    };
379}
380
381impl_integer!(i8);
382impl_integer!(i16);
383impl_integer!(i32);
384impl_integer!(i64);
385impl_integer!(i128);
386impl_integer!(isize);
387impl_integer!(u8);
388impl_integer!(u16);
389impl_integer!(u32);
390impl_integer!(u64);
391impl_integer!(u128);
392impl_integer!(usize);
393impl_integer!(NonZeroI8);
394impl_integer!(NonZeroI16);
395impl_integer!(NonZeroI32);
396impl_integer!(NonZeroI64);
397impl_integer!(NonZeroI128);
398impl_integer!(NonZeroIsize);
399impl_integer!(NonZeroU8);
400impl_integer!(NonZeroU16);
401impl_integer!(NonZeroU32);
402impl_integer!(NonZeroU64);
403impl_integer!(NonZeroU128);
404impl_integer!(NonZeroUsize);
405
406macro_rules! impl_integer_as_form_field {
407    ($type:ty) => {
408        impl AsFormField for $type {
409            type Type = IntegerField<$type>;
410
411            fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
412                let value = check_required(field)?;
413
414                let parsed: $type = value
415                    .parse()
416                    .map_err(|_| FormFieldValidationError::invalid_value(value))?;
417
418                if let Some(min) = field.custom_options.min {
419                    if parsed < min {
420                        return Err(FormFieldValidationError::minimum_value_not_met(min));
421                    }
422                }
423
424                if let Some(max) = field.custom_options.max {
425                    if parsed > max {
426                        return Err(FormFieldValidationError::maximum_value_exceeded(max));
427                    }
428                }
429
430                Ok(parsed)
431            }
432
433            fn to_field_value(&self) -> String {
434                self.to_string()
435            }
436        }
437    };
438}
439
440impl_integer_as_form_field!(i8);
441impl_integer_as_form_field!(i16);
442impl_integer_as_form_field!(i32);
443impl_integer_as_form_field!(i64);
444impl_integer_as_form_field!(i128);
445impl_integer_as_form_field!(isize);
446impl_integer_as_form_field!(u8);
447impl_integer_as_form_field!(u16);
448impl_integer_as_form_field!(u32);
449impl_integer_as_form_field!(u64);
450impl_integer_as_form_field!(u128);
451impl_integer_as_form_field!(usize);
452impl_integer_as_form_field!(NonZeroI8);
453impl_integer_as_form_field!(NonZeroI16);
454impl_integer_as_form_field!(NonZeroI32);
455impl_integer_as_form_field!(NonZeroI64);
456impl_integer_as_form_field!(NonZeroI128);
457impl_integer_as_form_field!(NonZeroIsize);
458impl_integer_as_form_field!(NonZeroU8);
459impl_integer_as_form_field!(NonZeroU16);
460impl_integer_as_form_field!(NonZeroU32);
461impl_integer_as_form_field!(NonZeroU64);
462impl_integer_as_form_field!(NonZeroU128);
463impl_integer_as_form_field!(NonZeroUsize);
464
465impl_form_field!(BoolField, BoolFieldOptions, "a boolean");
466
467/// Custom options for a [`BoolField`].
468#[derive(Debug, Default, Copy, Clone)]
469pub struct BoolFieldOptions {
470    /// If `true`, the field must be checked to be considered valid.
471    pub must_be_true: Option<bool>,
472}
473
474impl Display for BoolField {
475    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
476        let mut bool_input = HtmlTag::input("checkbox");
477        bool_input.attr("name", self.id());
478        bool_input.attr("id", self.id());
479        bool_input.attr("value", "1");
480
481        if self.custom_options.must_be_true.unwrap_or(false) {
482            bool_input.bool_attr("required");
483            return write!(f, "{}", bool_input.render());
484        }
485
486        if let Some(value) = &self.value
487            && value == "1"
488        {
489            bool_input.bool_attr("checked");
490        }
491
492        // Web browsers don't send anything when a checkbox is unchecked, so we
493        // need to add a hidden input to send a "false" value.
494        let mut hidden_input = HtmlTag::input("hidden");
495        hidden_input.attr("name", self.id());
496        hidden_input.attr("value", "0");
497        let hidden = hidden_input.render();
498
499        let checkbox = bool_input.render();
500        write!(f, "{}{}", hidden.as_str(), checkbox.as_str())
501    }
502}
503
504impl HtmlSafe for BoolField {}
505
506/// Implementation of `AsFormField` for `bool`.
507///
508/// This implementation converts the string values "true", "on", and "1" to
509/// `true`, and "false", "off", and "0" to `false`. It returns an error if the
510/// value is not one of these strings. If the field is required to be `true` by
511/// the field's options, it will return an error if the value is `false`.
512impl AsFormField for bool {
513    type Type = BoolField;
514
515    fn new_field(
516        mut options: FormFieldOptions,
517        custom_options: <Self::Type as FormField>::CustomOptions,
518    ) -> Self::Type {
519        options.required = false;
520        Self::Type::with_options(options, custom_options)
521    }
522
523    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
524        let value = check_required(field)?;
525        let value = if ["true", "on", "1"].contains(&value) {
526            true
527        } else if ["false", "off", "0"].contains(&value) {
528            false
529        } else {
530            return Err(FormFieldValidationError::invalid_value(value));
531        };
532
533        if field.custom_options.must_be_true.unwrap_or(false) && !value {
534            return Err(FormFieldValidationError::BooleanRequiredToBeTrue);
535        }
536        Ok(value.to_owned())
537    }
538
539    fn to_field_value(&self) -> String {
540        String::from(if *self { "1" } else { "0" })
541    }
542}
543
544impl<T: AsFormField> AsFormField for Option<T> {
545    type Type = T::Type;
546
547    fn new_field(
548        mut options: FormFieldOptions,
549        custom_options: <Self::Type as FormField>::CustomOptions,
550    ) -> Self::Type {
551        options.required = false;
552        Self::Type::with_options(options, custom_options)
553    }
554
555    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
556        let value = T::clean_value(field);
557        match value {
558            Ok(value) => Ok(Some(value)),
559            Err(FormFieldValidationError::Required) => Ok(None),
560            Err(error) => Err(error),
561        }
562    }
563
564    fn to_field_value(&self) -> String {
565        match self {
566            Some(value) => value.to_field_value(),
567            None => String::new(),
568        }
569    }
570}
571
572#[cfg(feature = "db")]
573impl<T: AsFormField> AsFormField for Auto<T> {
574    type Type = T::Type;
575
576    fn new_field(
577        mut options: FormFieldOptions,
578        custom_options: <Self::Type as FormField>::CustomOptions,
579    ) -> Self::Type {
580        options.required = false;
581        Self::Type::with_options(options, custom_options)
582    }
583
584    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError>
585    where
586        Self: Sized,
587    {
588        let value = T::clean_value(field);
589        match value {
590            Ok(value) => Ok(Auto::fixed(value)),
591            Err(FormFieldValidationError::Required) => Ok(Auto::auto()),
592            Err(error) => Err(error),
593        }
594    }
595
596    fn to_field_value(&self) -> String {
597        match self {
598            Auto::Fixed(value) => value.to_field_value(),
599            Auto::Auto => String::new(),
600        }
601    }
602}
603
604#[cfg(feature = "db")]
605impl<T> AsFormField for ForeignKey<T>
606where
607    T: Model,
608    <T as Model>::PrimaryKey: AsFormField,
609{
610    type Type = <<T as Model>::PrimaryKey as AsFormField>::Type;
611
612    fn new_field(
613        options: FormFieldOptions,
614        custom_options: <Self::Type as FormField>::CustomOptions,
615    ) -> Self::Type {
616        Self::Type::with_options(options, custom_options)
617    }
618
619    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError>
620    where
621        Self: Sized,
622    {
623        let value = <T as Model>::PrimaryKey::clean_value(field);
624        match value {
625            Ok(value) => Ok(ForeignKey::PrimaryKey(value)),
626            Err(error) => Err(error),
627        }
628    }
629
630    fn to_field_value(&self) -> String {
631        match self {
632            ForeignKey::PrimaryKey(primary_key) => primary_key.to_field_value(),
633            ForeignKey::Model(model) => model.primary_key().to_field_value(),
634        }
635    }
636}
637
638pub(crate) fn check_required<T: FormField>(field: &T) -> Result<&str, FormFieldValidationError> {
639    if let Some(value) = field.value() {
640        if value.is_empty() {
641            Err(FormFieldValidationError::Required)
642        } else {
643            Ok(value)
644        }
645    } else {
646        Err(FormFieldValidationError::Required)
647    }
648}
649
650impl_form_field!(FloatField, FloatFieldOptions, "a float",  T: Float);
651
652/// Custom options for a [`FloatField`].
653#[derive(Debug, Copy, Clone)]
654pub struct FloatFieldOptions<T> {
655    /// The minimum value of the field. Used to set the `min` attribute in the
656    /// HTML input element.
657    pub min: Option<T>,
658    /// The maximum value of the field. Used to set the `max` attribute in the
659    /// HTML input element.
660    pub max: Option<T>,
661}
662
663impl<T: Float> Default for FloatFieldOptions<T> {
664    fn default() -> Self {
665        Self {
666            min: T::MIN,
667            max: T::MAX,
668        }
669    }
670}
671
672impl<T: Float> Display for FloatField<T> {
673    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
674        let mut tag: HtmlTag = HtmlTag::input("number");
675        tag.attr("name", self.id());
676        tag.attr("id", self.id());
677        if self.options.required {
678            tag.bool_attr("required");
679        }
680
681        if let Some(min) = &self.custom_options.min {
682            tag.attr("min", min.to_string());
683        }
684        if let Some(max) = &self.custom_options.max {
685            tag.attr("max", max.to_string());
686        }
687        if let Some(value) = &self.value {
688            tag.attr("value", value);
689        }
690
691        write!(f, "{}", tag.render())
692    }
693}
694
695impl<T: Float> HtmlSafe for FloatField<T> {}
696
697/// A trait for types that can be represented as a float.
698///
699/// This trait is implemented for `f32` and `f64`.
700pub trait Float: Sized + ToString + Send {
701    /// The minimum value of the type.
702    ///
703    /// # Examples
704    ///
705    /// ```
706    /// use cot::form::fields::Float;
707    ///
708    /// assert_eq!(<f32 as Float>::MIN, Some(f32::MIN));
709    /// ```
710    const MIN: Option<Self>;
711    /// The maximum value of the type.
712    ///
713    /// # Examples
714    ///
715    /// ```
716    /// use cot::form::fields::Float;
717    ///
718    /// assert_eq!(<f32 as Float>::MAX, Some(f32::MAX));
719    /// ```
720    const MAX: Option<Self>;
721}
722
723macro_rules! impl_float {
724    ($type:ty) => {
725        impl Float for $type {
726            const MIN: Option<Self> = Some(Self::MIN);
727            const MAX: Option<Self> = Some(Self::MAX);
728        }
729    };
730}
731
732impl_float!(f32);
733impl_float!(f64);
734
735macro_rules! impl_float_as_form_field {
736    ($type:ty) => {
737        impl AsFormField for $type {
738            type Type = FloatField<$type>;
739
740            fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
741                let value = check_required(field)?;
742                let parsed: $type = value
743                    .parse()
744                    .map_err(|_| FormFieldValidationError::invalid_value(value))?;
745
746                if parsed.is_nan() || parsed.is_infinite() {
747                    return Err(FormFieldValidationError::from_static(
748                        "Cannot have NaN or inf as form input values",
749                    ));
750                }
751
752                if let Some(min) = field.custom_options.min {
753                    if parsed < min {
754                        return Err(FormFieldValidationError::minimum_value_not_met(min));
755                    }
756                }
757
758                if let Some(max) = field.custom_options.max {
759                    if parsed > max {
760                        return Err(FormFieldValidationError::maximum_value_exceeded(max));
761                    }
762                }
763
764                Ok(parsed)
765            }
766
767            fn to_field_value(&self) -> String {
768                self.to_string()
769            }
770        }
771    };
772}
773
774impl_float_as_form_field!(f32);
775impl_float_as_form_field!(f64);
776
777impl_form_field!(UrlField, UrlFieldOptions, "a URL");
778
779/// Custom options for a [`UrlField`].
780#[derive(Debug, Default, Copy, Clone)]
781pub struct UrlFieldOptions;
782
783impl Display for UrlField {
784    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
785        // no custom options
786        let _ = self.custom_options;
787        let mut tag = HtmlTag::input("url");
788        tag.attr("name", self.id());
789        tag.attr("id", self.id());
790        if self.options.required {
791            tag.bool_attr("required");
792        }
793        if let Some(value) = &self.value {
794            tag.attr("value", value);
795        }
796
797        write!(f, "{}", tag.render())
798    }
799}
800
801impl HtmlSafe for UrlField {}
802
803impl AsFormField for Url {
804    type Type = UrlField;
805
806    fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError>
807    where
808        Self: Sized,
809    {
810        let value = check_required(field)?;
811
812        Ok(value.parse()?)
813    }
814
815    fn to_field_value(&self) -> String {
816        self.as_str().to_owned()
817    }
818}
819
820#[cfg(test)]
821mod tests {
822    use super::*;
823    use crate::form::FormFieldValue;
824
825    #[test]
826    fn string_field_render() {
827        let field = StringField::with_options(
828            FormFieldOptions {
829                id: "test".to_owned(),
830                name: "test".to_owned(),
831                required: true,
832            },
833            StringFieldOptions {
834                max_length: Some(10),
835            },
836        );
837        let html = field.to_string();
838        assert!(html.contains("type=\"text\""));
839        assert!(html.contains("required"));
840        assert!(html.contains("maxlength=\"10\""));
841    }
842
843    #[cot::test]
844    async fn string_field_clean_value() {
845        let mut field = StringField::with_options(
846            FormFieldOptions {
847                id: "test".to_owned(),
848                name: "test".to_owned(),
849                required: true,
850            },
851            StringFieldOptions {
852                max_length: Some(10),
853            },
854        );
855        field
856            .set_value(FormFieldValue::new_text("test"))
857            .await
858            .unwrap();
859        let value = String::clean_value(&field).unwrap();
860        assert_eq!(value, "test");
861    }
862
863    #[cot::test]
864    async fn string_field_clean_required() {
865        let mut field = StringField::with_options(
866            FormFieldOptions {
867                id: "test".to_owned(),
868                name: "test".to_owned(),
869                required: true,
870            },
871            StringFieldOptions {
872                max_length: Some(10),
873            },
874        );
875        field.set_value(FormFieldValue::new_text("")).await.unwrap();
876        let value = String::clean_value(&field);
877        assert_eq!(value, Err(FormFieldValidationError::Required));
878    }
879
880    #[test]
881    fn password_field_render() {
882        let field = PasswordField::with_options(
883            FormFieldOptions {
884                id: "test".to_owned(),
885                name: "test".to_owned(),
886                required: true,
887            },
888            PasswordFieldOptions {
889                max_length: Some(10),
890            },
891        );
892        let html = field.to_string();
893        assert!(html.contains("type=\"password\""));
894        assert!(html.contains("required"));
895        assert!(html.contains("maxlength=\"10\""));
896    }
897    #[cot::test]
898    async fn password_field_clean_value() {
899        let mut field = PasswordField::with_options(
900            FormFieldOptions {
901                id: "test".to_owned(),
902                name: "test".to_owned(),
903                required: true,
904            },
905            PasswordFieldOptions {
906                max_length: Some(10),
907            },
908        );
909        field
910            .set_value(FormFieldValue::new_text("password"))
911            .await
912            .unwrap();
913        let value = Password::clean_value(&field).unwrap();
914        assert_eq!(value.as_str(), "password");
915    }
916
917    #[test]
918    fn email_field_render() {
919        let field = EmailField::with_options(
920            FormFieldOptions {
921                id: "test_id".to_owned(),
922                name: "test_name".to_owned(),
923                required: true,
924            },
925            EmailFieldOptions {
926                min_length: Some(10),
927                max_length: Some(50),
928            },
929        );
930
931        let html = field.to_string();
932        assert!(html.contains("type=\"email\""));
933        assert!(html.contains("required"));
934        assert!(html.contains("minlength=\"10\""));
935        assert!(html.contains("maxlength=\"50\""));
936        assert!(html.contains("name=\"test_id\""));
937        assert!(html.contains("id=\"test_id\""));
938    }
939
940    #[cot::test]
941    async fn email_field_clean_valid() {
942        let mut field = EmailField::with_options(
943            FormFieldOptions {
944                id: "email_test".to_owned(),
945                name: "email_test".to_owned(),
946                required: true,
947            },
948            EmailFieldOptions {
949                min_length: Some(10),
950                max_length: Some(50),
951            },
952        );
953
954        field
955            .set_value(FormFieldValue::new_text("user@example.com"))
956            .await
957            .unwrap();
958        let email = Email::clean_value(&field).unwrap();
959
960        assert_eq!(email.as_str(), "user@example.com");
961    }
962
963    #[cot::test]
964    async fn email_field_clean_invalid_format() {
965        let mut field = EmailField::with_options(
966            FormFieldOptions {
967                id: "email_test".to_owned(),
968                name: "email_test".to_owned(),
969                required: true,
970            },
971            EmailFieldOptions {
972                min_length: Some(10),
973                max_length: Some(50),
974            },
975        );
976
977        field
978            .set_value(FormFieldValue::new_text("invalid-email"))
979            .await
980            .unwrap();
981        let result = Email::clean_value(&field);
982
983        assert!(result.is_err());
984    }
985
986    #[cot::test]
987    async fn email_field_clean_exceeds_max_length() {
988        let mut field = EmailField::with_options(
989            FormFieldOptions {
990                id: "email_test".to_owned(),
991                name: "email_test".to_owned(),
992                required: true,
993            },
994            EmailFieldOptions {
995                min_length: Some(5),
996                max_length: Some(10),
997            },
998        );
999
1000        field
1001            .set_value(FormFieldValue::new_text("averylongemail@example.com"))
1002            .await
1003            .unwrap();
1004        let result = Email::clean_value(&field);
1005
1006        assert!(matches!(
1007            result,
1008            Err(FormFieldValidationError::MaximumLengthExceeded { max_length: _ })
1009        ));
1010    }
1011
1012    #[cot::test]
1013    async fn email_field_clean_below_min_length() {
1014        let mut field = EmailField::with_options(
1015            FormFieldOptions {
1016                id: "email_test".to_owned(),
1017                name: "email_test".to_owned(),
1018                required: true,
1019            },
1020            EmailFieldOptions {
1021                min_length: Some(5),
1022                max_length: Some(10),
1023            },
1024        );
1025
1026        field
1027            .set_value(FormFieldValue::new_text("cot"))
1028            .await
1029            .unwrap();
1030        let result = Email::clean_value(&field);
1031
1032        assert!(matches!(
1033            result,
1034            Err(FormFieldValidationError::MinimumLengthNotMet { min_length: _ })
1035        ));
1036    }
1037
1038    #[cot::test]
1039    async fn email_field_clean_invalid_length_options() {
1040        let mut field = EmailField::with_options(
1041            FormFieldOptions {
1042                id: "email_test".to_owned(),
1043                name: "email_test".to_owned(),
1044                required: true,
1045            },
1046            EmailFieldOptions {
1047                min_length: Some(50),
1048                max_length: Some(10),
1049            },
1050        );
1051
1052        field
1053            .set_value(FormFieldValue::new_text("user@example.com"))
1054            .await
1055            .unwrap();
1056        let result = Email::clean_value(&field);
1057
1058        assert!(result.is_err());
1059        if let Err(err) = result {
1060            let msg = err.to_string();
1061            assert!(msg.contains("min_length") && msg.contains("exceeds max_length"));
1062        }
1063    }
1064
1065    #[test]
1066    fn integer_field_render() {
1067        let field = IntegerField::<i32>::with_options(
1068            FormFieldOptions {
1069                id: "test".to_owned(),
1070                name: "test".to_owned(),
1071                required: true,
1072            },
1073            IntegerFieldOptions {
1074                min: Some(1),
1075                max: Some(10),
1076            },
1077        );
1078        let html = field.to_string();
1079        assert!(html.contains("type=\"number\""));
1080        assert!(html.contains("required"));
1081        assert!(html.contains("min=\"1\""));
1082        assert!(html.contains("max=\"10\""));
1083    }
1084
1085    #[cot::test]
1086    async fn integer_field_clean_value() {
1087        let mut field = IntegerField::<i32>::with_options(
1088            FormFieldOptions {
1089                id: "test".to_owned(),
1090                name: "test".to_owned(),
1091                required: true,
1092            },
1093            IntegerFieldOptions {
1094                min: Some(1),
1095                max: Some(10),
1096            },
1097        );
1098        field
1099            .set_value(FormFieldValue::new_text("5"))
1100            .await
1101            .unwrap();
1102        let value = i32::clean_value(&field).unwrap();
1103        assert_eq!(value, 5);
1104    }
1105
1106    #[cot::test]
1107    async fn integer_field_clean_value_below_min_value() {
1108        let mut field = IntegerField::<i32>::with_options(
1109            FormFieldOptions {
1110                id: "test".to_owned(),
1111                name: "test".to_owned(),
1112                required: true,
1113            },
1114            IntegerFieldOptions {
1115                min: Some(10),
1116                max: Some(50),
1117            },
1118        );
1119        field
1120            .set_value(FormFieldValue::new_text("5"))
1121            .await
1122            .unwrap();
1123        let value = i32::clean_value(&field);
1124        assert!(matches!(
1125            value,
1126            Err(FormFieldValidationError::MinimumValueNotMet { min_value: _ })
1127        ));
1128    }
1129
1130    #[cot::test]
1131    async fn integer_field_clean_value_above_max_value() {
1132        let mut field = IntegerField::<i32>::with_options(
1133            FormFieldOptions {
1134                id: "test".to_owned(),
1135                name: "test".to_owned(),
1136                required: true,
1137            },
1138            IntegerFieldOptions {
1139                min: Some(10),
1140                max: Some(50),
1141            },
1142        );
1143        field
1144            .set_value(FormFieldValue::new_text("100"))
1145            .await
1146            .unwrap();
1147        let value = i32::clean_value(&field);
1148        assert!(matches!(
1149            value,
1150            Err(FormFieldValidationError::MaximumValueExceeded { max_value: _ })
1151        ));
1152    }
1153
1154    #[test]
1155    fn bool_field_render() {
1156        let field = BoolField::with_options(
1157            FormFieldOptions {
1158                id: "test".to_owned(),
1159                name: "test".to_owned(),
1160                required: true,
1161            },
1162            BoolFieldOptions {
1163                must_be_true: Some(false),
1164            },
1165        );
1166        let html = field.to_string();
1167        assert!(html.contains("type=\"checkbox\""));
1168        assert!(html.contains("type=\"hidden\""));
1169        assert!(!html.contains("required"));
1170    }
1171
1172    #[test]
1173    fn bool_field_render_must_be_true() {
1174        let field = BoolField::with_options(
1175            FormFieldOptions {
1176                id: "test".to_owned(),
1177                name: "test".to_owned(),
1178                required: true,
1179            },
1180            BoolFieldOptions {
1181                must_be_true: Some(true),
1182            },
1183        );
1184        let html = field.to_string();
1185        assert!(html.contains("type=\"checkbox\""));
1186        assert!(!html.contains("type=\"hidden\""));
1187        assert!(html.contains("required"));
1188    }
1189
1190    #[cot::test]
1191    async fn bool_field_clean_value() {
1192        let mut field = BoolField::with_options(
1193            FormFieldOptions {
1194                id: "test".to_owned(),
1195                name: "test".to_owned(),
1196                required: true,
1197            },
1198            BoolFieldOptions {
1199                must_be_true: Some(true),
1200            },
1201        );
1202        field
1203            .set_value(FormFieldValue::new_text("true"))
1204            .await
1205            .unwrap();
1206        let value = bool::clean_value(&field).unwrap();
1207        assert!(value);
1208    }
1209
1210    #[test]
1211    fn float_field_render() {
1212        let field = FloatField::<f32>::with_options(
1213            FormFieldOptions {
1214                id: "test".to_owned(),
1215                name: "test".to_owned(),
1216                required: true,
1217            },
1218            FloatFieldOptions {
1219                min: Some(1.5),
1220                max: Some(10.7),
1221            },
1222        );
1223        let html = field.to_string();
1224        assert!(html.contains("type=\"number\""));
1225        assert!(html.contains("required"));
1226        assert!(html.contains("min=\"1.5\""));
1227        assert!(html.contains("max=\"10.7\""));
1228    }
1229
1230    #[cot::test]
1231    #[expect(clippy::float_cmp)]
1232    async fn float_field_clean_value() {
1233        let mut field = FloatField::<f32>::with_options(
1234            FormFieldOptions {
1235                id: "test".to_owned(),
1236                name: "test".to_owned(),
1237                required: true,
1238            },
1239            FloatFieldOptions {
1240                min: Some(1.0),
1241                max: Some(10.0),
1242            },
1243        );
1244        field
1245            .set_value(FormFieldValue::new_text("5.0"))
1246            .await
1247            .unwrap();
1248        let value = f32::clean_value(&field).unwrap();
1249        assert_eq!(value, 5.0f32);
1250    }
1251
1252    #[cot::test]
1253    async fn float_field_clean_value_min_value_not_met() {
1254        let mut field = FloatField::<f32>::with_options(
1255            FormFieldOptions {
1256                id: "test".to_owned(),
1257                name: "test".to_owned(),
1258                required: true,
1259            },
1260            FloatFieldOptions {
1261                min: Some(5.0),
1262                max: Some(10.0),
1263            },
1264        );
1265        field
1266            .set_value(FormFieldValue::new_text("2.0"))
1267            .await
1268            .unwrap();
1269        let value = f32::clean_value(&field);
1270        assert!(matches!(
1271            value,
1272            Err(FormFieldValidationError::MinimumValueNotMet { min_value: _ })
1273        ));
1274    }
1275
1276    #[cot::test]
1277    async fn float_field_clean_value_max_value_exceeded() {
1278        let mut field = FloatField::<f32>::with_options(
1279            FormFieldOptions {
1280                id: "test".to_owned(),
1281                name: "test".to_owned(),
1282                required: true,
1283            },
1284            FloatFieldOptions {
1285                min: Some(5.0),
1286                max: Some(10.0),
1287            },
1288        );
1289        field
1290            .set_value(FormFieldValue::new_text("20.0"))
1291            .await
1292            .unwrap();
1293        let value = f32::clean_value(&field);
1294        assert!(matches!(
1295            value,
1296            Err(FormFieldValidationError::MaximumValueExceeded { max_value: _ })
1297        ));
1298    }
1299
1300    #[cot::test]
1301    async fn float_field_clean_value_nan_and_inf() {
1302        let mut field = FloatField::<f32>::with_options(
1303            FormFieldOptions {
1304                id: "test".to_owned(),
1305                name: "test".to_owned(),
1306                required: true,
1307            },
1308            FloatFieldOptions {
1309                min: Some(1.0),
1310                max: Some(10.0),
1311            },
1312        );
1313        let bad_inputs = ["NaN", "inf"];
1314
1315        for &bad_input in &bad_inputs {
1316            field
1317                .set_value(FormFieldValue::new_text(bad_input))
1318                .await
1319                .unwrap();
1320            let value = f32::clean_value(&field);
1321            assert_eq!(
1322                value,
1323                Err(FormFieldValidationError::from_static(
1324                    "Cannot have NaN or inf as form input values"
1325                ))
1326            );
1327        }
1328    }
1329
1330    #[cot::test]
1331    async fn float_field_clean_required() {
1332        let mut field = FloatField::<f32>::with_options(
1333            FormFieldOptions {
1334                id: "test".to_owned(),
1335                name: "test".to_owned(),
1336                required: true,
1337            },
1338            FloatFieldOptions {
1339                min: Some(1.0),
1340                max: Some(10.0),
1341            },
1342        );
1343        field.set_value(FormFieldValue::new_text("")).await.unwrap();
1344        let value = f32::clean_value(&field);
1345        assert_eq!(value, Err(FormFieldValidationError::Required));
1346    }
1347
1348    #[cot::test]
1349    async fn url_field_clean_value() {
1350        let mut field = UrlField::with_options(
1351            FormFieldOptions {
1352                id: "test".to_owned(),
1353                name: "test".to_owned(),
1354                required: true,
1355            },
1356            UrlFieldOptions,
1357        );
1358        field
1359            .set_value(FormFieldValue::new_text("https://example.com"))
1360            .await
1361            .unwrap();
1362        let value = Url::clean_value(&field).unwrap();
1363        assert_eq!(
1364            value.as_str(),
1365            Url::new("https://example.com").unwrap().as_str()
1366        );
1367    }
1368
1369    #[cot::test]
1370    async fn url_field_render() {
1371        let mut field = UrlField::with_options(
1372            FormFieldOptions {
1373                id: "id_url".to_owned(),
1374                name: "url".to_owned(),
1375                required: true,
1376            },
1377            UrlFieldOptions,
1378        );
1379        field
1380            .set_value(FormFieldValue::new_text("http://example.com"))
1381            .await
1382            .unwrap();
1383        let html = field.to_string();
1384        assert!(html.contains("type=\"url\""));
1385        assert!(html.contains("required"));
1386        assert!(html.contains("value=\"http://example.com\""));
1387    }
1388
1389    #[cot::test]
1390    async fn url_field_clean_required() {
1391        let mut field = UrlField::with_options(
1392            FormFieldOptions {
1393                id: "id_url".to_owned(),
1394                name: "url".to_owned(),
1395                required: true,
1396            },
1397            UrlFieldOptions,
1398        );
1399        field.set_value(FormFieldValue::new_text("")).await.unwrap();
1400        let value = Url::clean_value(&field);
1401        assert_eq!(value, Err(FormFieldValidationError::Required));
1402    }
1403}