1use std::borrow::Cow;
2use std::fmt::{Debug, Display, Formatter};
3use std::num::{
4 NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
5 NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
6};
7
8#[cfg(feature = "db")]
9use cot::db::Auto;
10use rinja::filters::HtmlSafe;
11
12use crate::auth::{Password, PasswordHash};
13#[cfg(feature = "db")]
14use crate::db::LimitedString;
15use crate::form::{AsFormField, FormField, FormFieldOptions, FormFieldValidationError};
16use crate::html::HtmlTag;
17
18macro_rules! impl_form_field {
19 ($field_type_name:ident, $field_options_type_name:ident, $purpose:literal $(, $generic_param:ident $(: $generic_param_bound:ident $(+ $generic_param_bound_more:ident)*)?)?) => {
20 #[derive(Debug)]
21 #[doc = concat!("A form field for ", $purpose, ".")]
22 pub struct $field_type_name $(<$generic_param>)? {
23 options: FormFieldOptions,
24 custom_options: $field_options_type_name $(<$generic_param>)?,
25 value: Option<String>,
26 }
27
28 impl $(<$generic_param $(: $generic_param_bound $(+ $generic_param_bound_more)* )?>)? FormField for $field_type_name $(<$generic_param>)? {
29 type CustomOptions = $field_options_type_name $(<$generic_param>)?;
30
31 fn with_options(
32 options: FormFieldOptions,
33 custom_options: Self::CustomOptions,
34 ) -> Self {
35 Self {
36 options,
37 custom_options,
38 value: None,
39 }
40 }
41
42 fn options(&self) -> &FormFieldOptions {
43 &self.options
44 }
45
46 fn value(&self) -> Option<&str> {
47 self.value.as_deref()
48 }
49
50 fn set_value(&mut self, value: Cow<'_, str>) {
51 self.value = Some(value.into_owned());
52 }
53 }
54 };
55}
56
57impl_form_field!(StringField, StringFieldOptions, "a string");
58
59#[derive(Debug, Default, Copy, Clone)]
61pub struct StringFieldOptions {
62 pub max_length: Option<u32>,
65}
66
67impl Display for StringField {
68 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69 let mut tag = HtmlTag::input("text");
70 tag.attr("name", self.id());
71 tag.attr("id", self.id());
72 if self.options.required {
73 tag.bool_attr("required");
74 }
75 if let Some(max_length) = self.custom_options.max_length {
76 tag.attr("maxlength", &max_length.to_string());
77 }
78 if let Some(value) = &self.value {
79 tag.attr("value", value);
80 }
81
82 write!(f, "{}", tag.render())
83 }
84}
85
86impl HtmlSafe for StringField {}
87
88impl AsFormField for String {
89 type Type = StringField;
90
91 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
92 let value = check_required(field)?;
93
94 if let Some(max_length) = field.custom_options.max_length {
95 if value.len() > max_length as usize {
96 return Err(FormFieldValidationError::maximum_length_exceeded(
97 max_length,
98 ));
99 }
100 }
101 Ok(value.to_owned())
102 }
103
104 fn to_field_value(&self) -> String {
105 self.to_owned()
106 }
107}
108
109#[cfg(feature = "db")]
110impl<const LEN: u32> AsFormField for LimitedString<LEN> {
111 type Type = StringField;
112
113 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
114 let value = check_required(field)?;
115
116 if value.len() > LEN as usize {
117 return Err(FormFieldValidationError::maximum_length_exceeded(LEN));
118 }
119 Ok(LimitedString::new(value.to_owned()).expect("length has already been checked"))
120 }
121
122 fn to_field_value(&self) -> String {
123 self.to_string()
124 }
125}
126
127impl_form_field!(PasswordField, PasswordFieldOptions, "a password");
128
129#[derive(Debug, Default, Copy, Clone)]
131pub struct PasswordFieldOptions {
132 pub max_length: Option<u32>,
135}
136
137impl Display for PasswordField {
138 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
139 let mut tag = HtmlTag::input("password");
140 tag.attr("name", self.id());
141 tag.attr("id", self.id());
142 if self.options.required {
143 tag.bool_attr("required");
144 }
145 if let Some(max_length) = self.custom_options.max_length {
146 tag.attr("maxlength", &max_length.to_string());
147 }
148 write!(f, "{}", tag.render())
152 }
153}
154
155impl HtmlSafe for PasswordField {}
156
157impl AsFormField for Password {
158 type Type = PasswordField;
159
160 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
161 let value = check_required(field)?;
162
163 if let Some(max_length) = field.custom_options.max_length {
164 if value.len() > max_length as usize {
165 return Err(FormFieldValidationError::maximum_length_exceeded(
166 max_length,
167 ));
168 }
169 }
170
171 Ok(Password::new(value))
172 }
173
174 fn to_field_value(&self) -> String {
175 self.as_str().to_owned()
176 }
177}
178
179impl AsFormField for PasswordHash {
180 type Type = PasswordField;
181
182 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
183 let value = check_required(field)?;
184
185 if let Some(max_length) = field.custom_options.max_length {
186 if value.len() > max_length as usize {
187 return Err(FormFieldValidationError::maximum_length_exceeded(
188 max_length,
189 ));
190 }
191 }
192
193 Ok(PasswordHash::from_password(&Password::new(value)))
194 }
195
196 fn to_field_value(&self) -> String {
197 String::new()
199 }
200}
201
202impl_form_field!(IntegerField, IntegerFieldOptions, "an integer", T: Integer);
203
204#[derive(Debug, Copy, Clone)]
206pub struct IntegerFieldOptions<T> {
207 pub min: Option<T>,
210 pub max: Option<T>,
213}
214
215impl<T: Integer> Default for IntegerFieldOptions<T> {
216 fn default() -> Self {
217 Self {
218 min: T::MIN,
219 max: T::MAX,
220 }
221 }
222}
223
224impl<T: Integer> Display for IntegerField<T> {
225 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
226 let mut tag = HtmlTag::input("number");
227 tag.attr("name", self.id());
228 tag.attr("id", self.id());
229 if self.options.required {
230 tag.bool_attr("required");
231 }
232 if let Some(min) = &self.custom_options.min {
233 tag.attr("min", &min.to_string());
234 }
235 if let Some(max) = &self.custom_options.max {
236 tag.attr("max", &max.to_string());
237 }
238 if let Some(value) = &self.value {
239 tag.attr("value", value);
240 }
241
242 write!(f, "{}", tag.render())
243 }
244}
245
246impl<T: Integer> HtmlSafe for IntegerField<T> {}
247
248pub trait Integer: Sized + ToString {
259 const MIN: Option<Self>;
269 const MAX: Option<Self>;
279}
280
281macro_rules! impl_integer {
282 ($type:ty) => {
283 impl Integer for $type {
284 const MAX: Option<Self> = Some(Self::MAX);
285 const MIN: Option<Self> = Some(Self::MIN);
286 }
287 };
288}
289
290impl_integer!(i8);
291impl_integer!(i16);
292impl_integer!(i32);
293impl_integer!(i64);
294impl_integer!(i128);
295impl_integer!(isize);
296impl_integer!(u8);
297impl_integer!(u16);
298impl_integer!(u32);
299impl_integer!(u64);
300impl_integer!(u128);
301impl_integer!(usize);
302impl_integer!(NonZeroI8);
303impl_integer!(NonZeroI16);
304impl_integer!(NonZeroI32);
305impl_integer!(NonZeroI64);
306impl_integer!(NonZeroI128);
307impl_integer!(NonZeroIsize);
308impl_integer!(NonZeroU8);
309impl_integer!(NonZeroU16);
310impl_integer!(NonZeroU32);
311impl_integer!(NonZeroU64);
312impl_integer!(NonZeroU128);
313impl_integer!(NonZeroUsize);
314
315macro_rules! impl_integer_as_form_field {
316 ($type:ty) => {
317 impl AsFormField for $type {
318 type Type = IntegerField<$type>;
319
320 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
321 let value = check_required(field)?;
322
323 value
324 .parse()
325 .map_err(|_| FormFieldValidationError::invalid_value(value))
326 }
327
328 fn to_field_value(&self) -> String {
329 self.to_string()
330 }
331 }
332 };
333}
334
335impl_integer_as_form_field!(i8);
336impl_integer_as_form_field!(i16);
337impl_integer_as_form_field!(i32);
338impl_integer_as_form_field!(i64);
339impl_integer_as_form_field!(i128);
340impl_integer_as_form_field!(isize);
341impl_integer_as_form_field!(u8);
342impl_integer_as_form_field!(u16);
343impl_integer_as_form_field!(u32);
344impl_integer_as_form_field!(u64);
345impl_integer_as_form_field!(u128);
346impl_integer_as_form_field!(usize);
347impl_integer_as_form_field!(NonZeroI8);
348impl_integer_as_form_field!(NonZeroI16);
349impl_integer_as_form_field!(NonZeroI32);
350impl_integer_as_form_field!(NonZeroI64);
351impl_integer_as_form_field!(NonZeroI128);
352impl_integer_as_form_field!(NonZeroIsize);
353impl_integer_as_form_field!(NonZeroU8);
354impl_integer_as_form_field!(NonZeroU16);
355impl_integer_as_form_field!(NonZeroU32);
356impl_integer_as_form_field!(NonZeroU64);
357impl_integer_as_form_field!(NonZeroU128);
358impl_integer_as_form_field!(NonZeroUsize);
359
360impl_form_field!(BoolField, BoolFieldOptions, "a boolean");
361
362#[derive(Debug, Default, Copy, Clone)]
364pub struct BoolFieldOptions {
365 pub must_be_true: Option<bool>,
367}
368
369impl Display for BoolField {
370 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
371 let mut bool_input = HtmlTag::input("checkbox");
372 bool_input.attr("name", self.id());
373 bool_input.attr("id", self.id());
374 bool_input.attr("value", "1");
375
376 if self.custom_options.must_be_true.unwrap_or(false) {
377 bool_input.bool_attr("required");
378 return write!(f, "{}", bool_input.render());
379 }
380
381 if let Some(value) = &self.value {
382 if value == "1" {
383 bool_input.bool_attr("checked");
384 }
385 }
386
387 let mut hidden_input = HtmlTag::input("hidden");
390 hidden_input.attr("name", self.id());
391 hidden_input.attr("value", "0");
392 let hidden = hidden_input.render();
393
394 let checkbox = bool_input.render();
395 write!(f, "{}{}", hidden.as_str(), checkbox.as_str())
396 }
397}
398
399impl HtmlSafe for BoolField {}
400
401impl AsFormField for bool {
408 type Type = BoolField;
409
410 fn new_field(
411 mut options: FormFieldOptions,
412 custom_options: <Self::Type as FormField>::CustomOptions,
413 ) -> Self::Type {
414 options.required = false;
415 Self::Type::with_options(options, custom_options)
416 }
417
418 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
419 let value = check_required(field)?;
420 let value = if ["true", "on", "1"].contains(&value) {
421 true
422 } else if ["false", "off", "0"].contains(&value) {
423 false
424 } else {
425 return Err(FormFieldValidationError::invalid_value(value));
426 };
427
428 if field.custom_options.must_be_true.unwrap_or(false) && !value {
429 return Err(FormFieldValidationError::BooleanRequiredToBeTrue);
430 }
431 Ok(value.to_owned())
432 }
433
434 fn to_field_value(&self) -> String {
435 String::from(if *self { "1" } else { "0" })
436 }
437}
438
439impl<T: AsFormField> AsFormField for Option<T> {
440 type Type = T::Type;
441
442 fn new_field(
443 mut options: FormFieldOptions,
444 custom_options: <Self::Type as FormField>::CustomOptions,
445 ) -> Self::Type {
446 options.required = false;
447 Self::Type::with_options(options, custom_options)
448 }
449
450 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError> {
451 let value = T::clean_value(field);
452 match value {
453 Ok(value) => Ok(Some(value)),
454 Err(FormFieldValidationError::Required) => Ok(None),
455 Err(error) => Err(error),
456 }
457 }
458
459 fn to_field_value(&self) -> String {
460 match self {
461 Some(value) => value.to_field_value(),
462 None => String::new(),
463 }
464 }
465}
466
467#[cfg(feature = "db")]
468impl<T: AsFormField> AsFormField for Auto<T> {
469 type Type = T::Type;
470
471 fn new_field(
472 mut options: FormFieldOptions,
473 custom_options: <Self::Type as FormField>::CustomOptions,
474 ) -> Self::Type {
475 options.required = false;
476 Self::Type::with_options(options, custom_options)
477 }
478
479 fn clean_value(field: &Self::Type) -> Result<Self, FormFieldValidationError>
480 where
481 Self: Sized,
482 {
483 let value = T::clean_value(field);
484 match value {
485 Ok(value) => Ok(Auto::fixed(value)),
486 Err(FormFieldValidationError::Required) => Ok(Auto::auto()),
487 Err(error) => Err(error),
488 }
489 }
490
491 fn to_field_value(&self) -> String {
492 match self {
493 Auto::Fixed(value) => value.to_field_value(),
494 Auto::Auto => String::new(),
495 }
496 }
497}
498
499fn check_required<T: FormField>(field: &T) -> Result<&str, FormFieldValidationError> {
500 if let Some(value) = field.value() {
501 if value.is_empty() {
502 Err(FormFieldValidationError::Required)
503 } else {
504 Ok(value)
505 }
506 } else {
507 Err(FormFieldValidationError::Required)
508 }
509}
510
511#[cfg(test)]
512mod tests {
513 use std::borrow::Cow;
514
515 use super::*;
516
517 #[test]
518 fn string_field_render() {
519 let field = StringField::with_options(
520 FormFieldOptions {
521 id: "test".to_owned(),
522 name: "test".to_owned(),
523 required: true,
524 },
525 StringFieldOptions {
526 max_length: Some(10),
527 },
528 );
529 let html = field.to_string();
530 assert!(html.contains("type=\"text\""));
531 assert!(html.contains("required"));
532 assert!(html.contains("maxlength=\"10\""));
533 }
534
535 #[test]
536 fn password_field_render() {
537 let field = PasswordField::with_options(
538 FormFieldOptions {
539 id: "test".to_owned(),
540 name: "test".to_owned(),
541 required: true,
542 },
543 PasswordFieldOptions {
544 max_length: Some(10),
545 },
546 );
547 let html = field.to_string();
548 assert!(html.contains("type=\"password\""));
549 assert!(html.contains("required"));
550 assert!(html.contains("maxlength=\"10\""));
551 }
552
553 #[test]
554 fn integer_field_render() {
555 let field = IntegerField::<i32>::with_options(
556 FormFieldOptions {
557 id: "test".to_owned(),
558 name: "test".to_owned(),
559 required: true,
560 },
561 IntegerFieldOptions {
562 min: Some(1),
563 max: Some(10),
564 },
565 );
566 let html = field.to_string();
567 assert!(html.contains("type=\"number\""));
568 assert!(html.contains("required"));
569 assert!(html.contains("min=\"1\""));
570 assert!(html.contains("max=\"10\""));
571 }
572
573 #[test]
574 fn bool_field_render() {
575 let field = BoolField::with_options(
576 FormFieldOptions {
577 id: "test".to_owned(),
578 name: "test".to_owned(),
579 required: true,
580 },
581 BoolFieldOptions {
582 must_be_true: Some(false),
583 },
584 );
585 let html = field.to_string();
586 assert!(html.contains("type=\"checkbox\""));
587 assert!(html.contains("type=\"hidden\""));
588 assert!(!html.contains("required"));
589 }
590
591 #[test]
592 fn bool_field_render_must_be_true() {
593 let field = BoolField::with_options(
594 FormFieldOptions {
595 id: "test".to_owned(),
596 name: "test".to_owned(),
597 required: true,
598 },
599 BoolFieldOptions {
600 must_be_true: Some(true),
601 },
602 );
603 let html = field.to_string();
604 assert!(html.contains("type=\"checkbox\""));
605 assert!(!html.contains("type=\"hidden\""));
606 assert!(html.contains("required"));
607 }
608
609 #[test]
610 fn string_field_clean_value() {
611 let mut field = StringField::with_options(
612 FormFieldOptions {
613 id: "test".to_owned(),
614 name: "test".to_owned(),
615 required: true,
616 },
617 StringFieldOptions {
618 max_length: Some(10),
619 },
620 );
621 field.set_value(Cow::Borrowed("test"));
622 let value = String::clean_value(&field).unwrap();
623 assert_eq!(value, "test");
624 }
625
626 #[test]
627 fn string_field_clean_required() {
628 let mut field = StringField::with_options(
629 FormFieldOptions {
630 id: "test".to_owned(),
631 name: "test".to_owned(),
632 required: true,
633 },
634 StringFieldOptions {
635 max_length: Some(10),
636 },
637 );
638 field.set_value(Cow::Borrowed(""));
639 let value = String::clean_value(&field);
640 assert_eq!(value, Err(FormFieldValidationError::Required));
641 }
642
643 #[test]
644 fn password_field_clean_value() {
645 let mut field = PasswordField::with_options(
646 FormFieldOptions {
647 id: "test".to_owned(),
648 name: "test".to_owned(),
649 required: true,
650 },
651 PasswordFieldOptions {
652 max_length: Some(10),
653 },
654 );
655 field.set_value(Cow::Borrowed("password"));
656 let value = Password::clean_value(&field).unwrap();
657 assert_eq!(value.as_str(), "password");
658 }
659
660 #[test]
661 fn integer_field_clean_value() {
662 let mut field = IntegerField::<i32>::with_options(
663 FormFieldOptions {
664 id: "test".to_owned(),
665 name: "test".to_owned(),
666 required: true,
667 },
668 IntegerFieldOptions {
669 min: Some(1),
670 max: Some(10),
671 },
672 );
673 field.set_value(Cow::Borrowed("5"));
674 let value = i32::clean_value(&field).unwrap();
675 assert_eq!(value, 5);
676 }
677
678 #[test]
679 fn bool_field_clean_value() {
680 let mut field = BoolField::with_options(
681 FormFieldOptions {
682 id: "test".to_owned(),
683 name: "test".to_owned(),
684 required: true,
685 },
686 BoolFieldOptions {
687 must_be_true: Some(true),
688 },
689 );
690 field.set_value(Cow::Borrowed("true"));
691 let value = bool::clean_value(&field).unwrap();
692 assert!(value);
693 }
694}