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#[derive(Debug, Default, Copy, Clone)]
77pub struct StringFieldOptions {
78 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#[derive(Debug, Default, Copy, Clone)]
147pub struct PasswordFieldOptions {
148 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 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 String::new()
215 }
216}
217
218impl_form_field!(EmailField, EmailFieldOptions, "an email");
219
220#[derive(Debug, Default, Copy, Clone)]
222pub struct EmailFieldOptions {
223 pub max_length: Option<u32>,
226 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#[derive(Debug, Copy, Clone)]
297pub struct IntegerFieldOptions<T> {
298 pub min: Option<T>,
301 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
339pub trait Integer: Sized + ToString + Send {
350 const MIN: Option<Self>;
360 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#[derive(Debug, Default, Copy, Clone)]
469pub struct BoolFieldOptions {
470 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 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
506impl 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#[derive(Debug, Copy, Clone)]
654pub struct FloatFieldOptions<T> {
655 pub min: Option<T>,
658 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
697pub trait Float: Sized + ToString + Send {
701 const MIN: Option<Self>;
711 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#[derive(Debug, Default, Copy, Clone)]
781pub struct UrlFieldOptions;
782
783impl Display for UrlField {
784 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
785 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}