1use std::collections::HashMap;
32
33use chrono::{DateTime, Utc};
34use serde::{Deserialize, Serialize};
35use serde_json::Value;
36
37use super::ExtensionBlock;
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41#[serde(tag = "fieldType", rename_all = "camelCase")]
42pub enum FormField {
43 TextInput(TextInputField),
45 TextArea(TextAreaField),
47 Checkbox(CheckboxField),
49 RadioGroup(RadioGroupField),
51 Dropdown(DropdownField),
53 DatePicker(DatePickerField),
55 Signature(SignatureField),
57}
58
59impl FormField {
60 #[must_use]
62 pub fn from_extension(ext: &ExtensionBlock) -> Option<Self> {
63 if ext.namespace != "forms" {
64 return None;
65 }
66
67 match ext.block_type.as_str() {
68 "textInput" => serde_json::from_value(ext.attributes.clone())
69 .ok()
70 .map(FormField::TextInput),
71 "textArea" => serde_json::from_value(ext.attributes.clone())
72 .ok()
73 .map(FormField::TextArea),
74 "checkbox" => serde_json::from_value(ext.attributes.clone())
75 .ok()
76 .map(FormField::Checkbox),
77 "radioGroup" => serde_json::from_value(ext.attributes.clone())
78 .ok()
79 .map(FormField::RadioGroup),
80 "dropdown" => serde_json::from_value(ext.attributes.clone())
81 .ok()
82 .map(FormField::Dropdown),
83 "datePicker" => serde_json::from_value(ext.attributes.clone())
84 .ok()
85 .map(FormField::DatePicker),
86 "signature" => serde_json::from_value(ext.attributes.clone())
87 .ok()
88 .map(FormField::Signature),
89 _ => None,
90 }
91 }
92
93 #[must_use]
95 pub fn id(&self) -> Option<&str> {
96 match self {
97 Self::TextInput(f) => f.id.as_deref(),
98 Self::TextArea(f) => f.id.as_deref(),
99 Self::Checkbox(f) => f.id.as_deref(),
100 Self::RadioGroup(f) => f.id.as_deref(),
101 Self::Dropdown(f) => f.id.as_deref(),
102 Self::DatePicker(f) => f.id.as_deref(),
103 Self::Signature(f) => f.id.as_deref(),
104 }
105 }
106
107 #[must_use]
109 pub fn label(&self) -> &str {
110 match self {
111 Self::TextInput(f) => &f.label,
112 Self::TextArea(f) => &f.label,
113 Self::Checkbox(f) => &f.label,
114 Self::RadioGroup(f) => &f.label,
115 Self::Dropdown(f) => &f.label,
116 Self::DatePicker(f) => &f.label,
117 Self::Signature(f) => &f.label,
118 }
119 }
120
121 #[must_use]
123 pub fn is_required(&self) -> bool {
124 match self {
125 Self::TextInput(f) => f.required,
126 Self::TextArea(f) => f.required,
127 Self::Checkbox(f) => f.required,
128 Self::RadioGroup(f) => f.required,
129 Self::Dropdown(f) => f.required,
130 Self::DatePicker(f) => f.required,
131 Self::Signature(f) => f.required,
132 }
133 }
134
135 #[must_use]
137 pub fn validation(&self) -> Option<&FormValidation> {
138 match self {
139 Self::TextInput(f) => f.validation.as_ref(),
140 Self::TextArea(f) => f.validation.as_ref(),
141 Self::DatePicker(f) => f.validation.as_ref(),
142 Self::Checkbox(_) | Self::RadioGroup(_) | Self::Dropdown(_) | Self::Signature(_) => {
143 None
144 }
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct TextInputField {
153 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub id: Option<String>,
156
157 pub label: String,
159
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub placeholder: Option<String>,
163
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub default_value: Option<String>,
167
168 #[serde(default)]
170 pub required: bool,
171
172 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
174 pub readonly: bool,
175
176 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub input_type: Option<String>,
179
180 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub validation: Option<FormValidation>,
183
184 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub conditional_validation: Option<ConditionalValidation>,
187}
188
189impl TextInputField {
190 #[must_use]
192 pub fn new(label: impl Into<String>) -> Self {
193 Self {
194 id: None,
195 label: label.into(),
196 placeholder: None,
197 default_value: None,
198 required: false,
199 readonly: false,
200 input_type: None,
201 validation: None,
202 conditional_validation: None,
203 }
204 }
205
206 #[must_use]
208 pub fn with_id(mut self, id: impl Into<String>) -> Self {
209 self.id = Some(id.into());
210 self
211 }
212
213 #[must_use]
215 pub fn with_placeholder(mut self, placeholder: impl Into<String>) -> Self {
216 self.placeholder = Some(placeholder.into());
217 self
218 }
219
220 #[must_use]
222 pub fn with_default(mut self, value: impl Into<String>) -> Self {
223 self.default_value = Some(value.into());
224 self
225 }
226
227 #[must_use]
229 pub const fn required(mut self) -> Self {
230 self.required = true;
231 self
232 }
233
234 #[must_use]
236 pub const fn readonly(mut self) -> Self {
237 self.readonly = true;
238 self
239 }
240
241 #[must_use]
243 pub fn with_input_type(mut self, input_type: impl Into<String>) -> Self {
244 self.input_type = Some(input_type.into());
245 self
246 }
247
248 #[must_use]
250 pub fn with_validation(mut self, validation: FormValidation) -> Self {
251 self.validation = Some(validation);
252 self
253 }
254
255 #[must_use]
257 pub fn with_conditional_validation(mut self, cv: ConditionalValidation) -> Self {
258 self.conditional_validation = Some(cv);
259 self
260 }
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct TextAreaField {
267 #[serde(default, skip_serializing_if = "Option::is_none")]
269 pub id: Option<String>,
270
271 pub label: String,
273
274 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub placeholder: Option<String>,
277
278 #[serde(default, skip_serializing_if = "Option::is_none")]
280 pub default_value: Option<String>,
281
282 #[serde(default)]
284 pub required: bool,
285
286 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
288 pub readonly: bool,
289
290 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub rows: Option<u32>,
293
294 #[serde(default, skip_serializing_if = "Option::is_none")]
296 pub max_length: Option<usize>,
297
298 #[serde(default, skip_serializing_if = "Option::is_none")]
300 pub validation: Option<FormValidation>,
301
302 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub conditional_validation: Option<ConditionalValidation>,
305}
306
307impl TextAreaField {
308 #[must_use]
310 pub fn new(label: impl Into<String>) -> Self {
311 Self {
312 id: None,
313 label: label.into(),
314 placeholder: None,
315 default_value: None,
316 required: false,
317 readonly: false,
318 rows: None,
319 max_length: None,
320 validation: None,
321 conditional_validation: None,
322 }
323 }
324
325 #[must_use]
327 pub fn with_id(mut self, id: impl Into<String>) -> Self {
328 self.id = Some(id.into());
329 self
330 }
331
332 #[must_use]
334 pub const fn with_rows(mut self, rows: u32) -> Self {
335 self.rows = Some(rows);
336 self
337 }
338
339 #[must_use]
341 pub const fn with_max_length(mut self, max_length: usize) -> Self {
342 self.max_length = Some(max_length);
343 self
344 }
345
346 #[must_use]
348 pub const fn required(mut self) -> Self {
349 self.required = true;
350 self
351 }
352
353 #[must_use]
355 pub fn with_conditional_validation(mut self, cv: ConditionalValidation) -> Self {
356 self.conditional_validation = Some(cv);
357 self
358 }
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
363#[serde(rename_all = "camelCase")]
364pub struct CheckboxField {
365 #[serde(default, skip_serializing_if = "Option::is_none")]
367 pub id: Option<String>,
368
369 pub label: String,
371
372 #[serde(default)]
374 pub default_checked: bool,
375
376 #[serde(default)]
378 pub required: bool,
379
380 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
382 pub readonly: bool,
383
384 #[serde(default, skip_serializing_if = "Option::is_none")]
386 pub conditional_validation: Option<ConditionalValidation>,
387}
388
389impl CheckboxField {
390 #[must_use]
392 pub fn new(label: impl Into<String>) -> Self {
393 Self {
394 id: None,
395 label: label.into(),
396 default_checked: false,
397 required: false,
398 readonly: false,
399 conditional_validation: None,
400 }
401 }
402
403 #[must_use]
405 pub fn with_id(mut self, id: impl Into<String>) -> Self {
406 self.id = Some(id.into());
407 self
408 }
409
410 #[must_use]
412 pub const fn checked(mut self) -> Self {
413 self.default_checked = true;
414 self
415 }
416
417 #[must_use]
419 pub const fn required(mut self) -> Self {
420 self.required = true;
421 self
422 }
423
424 #[must_use]
426 pub fn with_conditional_validation(mut self, cv: ConditionalValidation) -> Self {
427 self.conditional_validation = Some(cv);
428 self
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct RadioOption {
436 pub value: String,
438
439 pub label: String,
441
442 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
444 pub disabled: bool,
445}
446
447impl RadioOption {
448 #[must_use]
450 pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
451 Self {
452 value: value.into(),
453 label: label.into(),
454 disabled: false,
455 }
456 }
457}
458
459#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct RadioGroupField {
463 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub id: Option<String>,
466
467 pub label: String,
469
470 pub options: Vec<RadioOption>,
472
473 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub default_value: Option<String>,
476
477 #[serde(default)]
479 pub required: bool,
480
481 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
483 pub readonly: bool,
484
485 #[serde(default, skip_serializing_if = "Option::is_none")]
487 pub conditional_validation: Option<ConditionalValidation>,
488}
489
490impl RadioGroupField {
491 #[must_use]
493 pub fn new(label: impl Into<String>, options: Vec<RadioOption>) -> Self {
494 Self {
495 id: None,
496 label: label.into(),
497 options,
498 default_value: None,
499 required: false,
500 readonly: false,
501 conditional_validation: None,
502 }
503 }
504
505 #[must_use]
507 pub fn with_id(mut self, id: impl Into<String>) -> Self {
508 self.id = Some(id.into());
509 self
510 }
511
512 #[must_use]
514 pub fn with_default(mut self, value: impl Into<String>) -> Self {
515 self.default_value = Some(value.into());
516 self
517 }
518
519 #[must_use]
521 pub const fn required(mut self) -> Self {
522 self.required = true;
523 self
524 }
525
526 #[must_use]
528 pub fn with_conditional_validation(mut self, cv: ConditionalValidation) -> Self {
529 self.conditional_validation = Some(cv);
530 self
531 }
532}
533
534#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
536#[serde(rename_all = "camelCase")]
537pub struct DropdownOption {
538 pub value: String,
540
541 pub label: String,
543
544 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
546 pub disabled: bool,
547
548 #[serde(default, skip_serializing_if = "Option::is_none")]
550 pub group: Option<String>,
551}
552
553impl DropdownOption {
554 #[must_use]
556 pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
557 Self {
558 value: value.into(),
559 label: label.into(),
560 disabled: false,
561 group: None,
562 }
563 }
564
565 #[must_use]
567 pub fn with_group(mut self, group: impl Into<String>) -> Self {
568 self.group = Some(group.into());
569 self
570 }
571}
572
573#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
575#[serde(rename_all = "camelCase")]
576pub struct DropdownField {
577 #[serde(default, skip_serializing_if = "Option::is_none")]
579 pub id: Option<String>,
580
581 pub label: String,
583
584 pub options: Vec<DropdownOption>,
586
587 #[serde(default, skip_serializing_if = "Option::is_none")]
589 pub default_value: Option<String>,
590
591 #[serde(default, skip_serializing_if = "Option::is_none")]
593 pub placeholder: Option<String>,
594
595 #[serde(default)]
597 pub required: bool,
598
599 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
601 pub readonly: bool,
602
603 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
605 pub multiple: bool,
606
607 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub conditional_validation: Option<ConditionalValidation>,
610}
611
612impl DropdownField {
613 #[must_use]
615 pub fn new(label: impl Into<String>, options: Vec<DropdownOption>) -> Self {
616 Self {
617 id: None,
618 label: label.into(),
619 options,
620 default_value: None,
621 placeholder: None,
622 required: false,
623 readonly: false,
624 multiple: false,
625 conditional_validation: None,
626 }
627 }
628
629 #[must_use]
631 pub fn with_id(mut self, id: impl Into<String>) -> Self {
632 self.id = Some(id.into());
633 self
634 }
635
636 #[must_use]
638 pub fn with_placeholder(mut self, placeholder: impl Into<String>) -> Self {
639 self.placeholder = Some(placeholder.into());
640 self
641 }
642
643 #[must_use]
645 pub fn with_default(mut self, value: impl Into<String>) -> Self {
646 self.default_value = Some(value.into());
647 self
648 }
649
650 #[must_use]
652 pub const fn required(mut self) -> Self {
653 self.required = true;
654 self
655 }
656
657 #[must_use]
659 pub const fn multiple(mut self) -> Self {
660 self.multiple = true;
661 self
662 }
663
664 #[must_use]
666 pub fn with_conditional_validation(mut self, cv: ConditionalValidation) -> Self {
667 self.conditional_validation = Some(cv);
668 self
669 }
670}
671
672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
674#[serde(rename_all = "camelCase")]
675pub struct DatePickerField {
676 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub id: Option<String>,
679
680 pub label: String,
682
683 #[serde(default)]
685 pub mode: DatePickerMode,
686
687 #[serde(default, skip_serializing_if = "Option::is_none")]
689 pub default_value: Option<String>,
690
691 #[serde(default, skip_serializing_if = "Option::is_none")]
693 pub min: Option<String>,
694
695 #[serde(default, skip_serializing_if = "Option::is_none")]
697 pub max: Option<String>,
698
699 #[serde(default)]
701 pub required: bool,
702
703 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
705 pub readonly: bool,
706
707 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub validation: Option<FormValidation>,
710
711 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub conditional_validation: Option<ConditionalValidation>,
714}
715
716#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
718#[serde(rename_all = "lowercase")]
719pub enum DatePickerMode {
720 #[default]
722 Date,
723 Time,
725 Datetime,
727}
728
729impl DatePickerField {
730 #[must_use]
732 pub fn new(label: impl Into<String>) -> Self {
733 Self {
734 id: None,
735 label: label.into(),
736 mode: DatePickerMode::Date,
737 default_value: None,
738 min: None,
739 max: None,
740 required: false,
741 readonly: false,
742 validation: None,
743 conditional_validation: None,
744 }
745 }
746
747 #[must_use]
749 pub fn with_id(mut self, id: impl Into<String>) -> Self {
750 self.id = Some(id.into());
751 self
752 }
753
754 #[must_use]
756 pub const fn with_mode(mut self, mode: DatePickerMode) -> Self {
757 self.mode = mode;
758 self
759 }
760
761 #[must_use]
763 pub fn with_min(mut self, min: impl Into<String>) -> Self {
764 self.min = Some(min.into());
765 self
766 }
767
768 #[must_use]
770 pub fn with_max(mut self, max: impl Into<String>) -> Self {
771 self.max = Some(max.into());
772 self
773 }
774
775 #[must_use]
777 pub const fn required(mut self) -> Self {
778 self.required = true;
779 self
780 }
781
782 #[must_use]
784 pub fn with_conditional_validation(mut self, cv: ConditionalValidation) -> Self {
785 self.conditional_validation = Some(cv);
786 self
787 }
788}
789
790#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
792#[serde(rename_all = "camelCase")]
793pub struct SignatureField {
794 #[serde(default, skip_serializing_if = "Option::is_none")]
796 pub id: Option<String>,
797
798 pub label: String,
800
801 #[serde(default)]
803 pub required: bool,
804
805 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
807 pub readonly: bool,
808
809 #[serde(default, skip_serializing_if = "Option::is_none")]
811 pub legal_text: Option<String>,
812
813 #[serde(default, skip_serializing_if = "Option::is_none")]
815 pub conditional_validation: Option<ConditionalValidation>,
816}
817
818impl SignatureField {
819 #[must_use]
821 pub fn new(label: impl Into<String>) -> Self {
822 Self {
823 id: None,
824 label: label.into(),
825 required: false,
826 readonly: false,
827 legal_text: None,
828 conditional_validation: None,
829 }
830 }
831
832 #[must_use]
834 pub fn with_id(mut self, id: impl Into<String>) -> Self {
835 self.id = Some(id.into());
836 self
837 }
838
839 #[must_use]
841 pub fn with_legal_text(mut self, text: impl Into<String>) -> Self {
842 self.legal_text = Some(text.into());
843 self
844 }
845
846 #[must_use]
848 pub const fn required(mut self) -> Self {
849 self.required = true;
850 self
851 }
852
853 #[must_use]
855 pub fn with_conditional_validation(mut self, cv: ConditionalValidation) -> Self {
856 self.conditional_validation = Some(cv);
857 self
858 }
859}
860
861#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
863#[serde(rename_all = "camelCase")]
864pub struct FormValidation {
865 pub rules: Vec<ValidationRule>,
867}
868
869impl FormValidation {
870 #[must_use]
872 pub fn new(rules: Vec<ValidationRule>) -> Self {
873 Self { rules }
874 }
875
876 #[must_use]
878 pub fn with_rule(rule: ValidationRule) -> Self {
879 Self { rules: vec![rule] }
880 }
881}
882
883#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
888#[serde(rename_all = "camelCase")]
889pub struct ConditionalValidation {
890 pub when: Condition,
892
893 pub then: ConditionalAction,
895}
896
897impl ConditionalValidation {
898 #[must_use]
900 pub fn new(when: Condition, then: ConditionalAction) -> Self {
901 Self { when, then }
902 }
903}
904
905#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
907#[serde(rename_all = "camelCase")]
908pub struct Condition {
909 pub field: String,
911
912 #[serde(flatten)]
914 pub operator: ConditionOperator,
915}
916
917impl Condition {
918 #[must_use]
920 pub fn equals(field: impl Into<String>, value: Value) -> Self {
921 Self {
922 field: field.into(),
923 operator: ConditionOperator {
924 equals: Some(value),
925 not_equals: None,
926 is_empty: None,
927 is_not_empty: None,
928 },
929 }
930 }
931
932 #[must_use]
934 pub fn not_equals(field: impl Into<String>, value: Value) -> Self {
935 Self {
936 field: field.into(),
937 operator: ConditionOperator {
938 equals: None,
939 not_equals: Some(value),
940 is_empty: None,
941 is_not_empty: None,
942 },
943 }
944 }
945
946 #[must_use]
948 pub fn is_empty(field: impl Into<String>) -> Self {
949 Self {
950 field: field.into(),
951 operator: ConditionOperator {
952 equals: None,
953 not_equals: None,
954 is_empty: Some(true),
955 is_not_empty: None,
956 },
957 }
958 }
959
960 #[must_use]
962 pub fn is_not_empty(field: impl Into<String>) -> Self {
963 Self {
964 field: field.into(),
965 operator: ConditionOperator {
966 equals: None,
967 not_equals: None,
968 is_empty: None,
969 is_not_empty: Some(true),
970 },
971 }
972 }
973}
974
975#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
980#[serde(rename_all = "camelCase")]
981pub struct ConditionOperator {
982 #[serde(default, skip_serializing_if = "Option::is_none")]
984 pub equals: Option<Value>,
985
986 #[serde(default, skip_serializing_if = "Option::is_none")]
988 pub not_equals: Option<Value>,
989
990 #[serde(default, skip_serializing_if = "Option::is_none")]
992 pub is_empty: Option<bool>,
993
994 #[serde(default, skip_serializing_if = "Option::is_none")]
996 pub is_not_empty: Option<bool>,
997}
998
999#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1001#[serde(rename_all = "camelCase")]
1002pub struct ConditionalAction {
1003 #[serde(default, skip_serializing_if = "Option::is_none")]
1005 pub required: Option<bool>,
1006
1007 #[serde(default, skip_serializing_if = "Option::is_none")]
1009 pub validation: Option<FormValidation>,
1010}
1011
1012impl ConditionalAction {
1013 #[must_use]
1015 pub fn require() -> Self {
1016 Self {
1017 required: Some(true),
1018 validation: None,
1019 }
1020 }
1021
1022 #[must_use]
1024 pub fn with_validation(validation: FormValidation) -> Self {
1025 Self {
1026 required: None,
1027 validation: Some(validation),
1028 }
1029 }
1030
1031 #[must_use]
1033 pub fn require_with_validation(validation: FormValidation) -> Self {
1034 Self {
1035 required: Some(true),
1036 validation: Some(validation),
1037 }
1038 }
1039}
1040
1041#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1043#[serde(tag = "type", rename_all = "camelCase")]
1044pub enum ValidationRule {
1045 Required {
1047 #[serde(default, skip_serializing_if = "Option::is_none")]
1049 message: Option<String>,
1050 },
1051 MinLength {
1053 value: usize,
1055 #[serde(default, skip_serializing_if = "Option::is_none")]
1057 message: Option<String>,
1058 },
1059 MaxLength {
1061 value: usize,
1063 #[serde(default, skip_serializing_if = "Option::is_none")]
1065 message: Option<String>,
1066 },
1067 Pattern {
1069 pattern: String,
1071 #[serde(default, skip_serializing_if = "Option::is_none")]
1073 message: Option<String>,
1074 },
1075 Email {
1077 #[serde(default, skip_serializing_if = "Option::is_none")]
1079 message: Option<String>,
1080 },
1081 Url {
1083 #[serde(default, skip_serializing_if = "Option::is_none")]
1085 message: Option<String>,
1086 },
1087 Min {
1089 value: i64,
1091 #[serde(default, skip_serializing_if = "Option::is_none")]
1093 message: Option<String>,
1094 },
1095 Max {
1097 value: i64,
1099 #[serde(default, skip_serializing_if = "Option::is_none")]
1101 message: Option<String>,
1102 },
1103 ContainsUppercase {
1105 #[serde(default, skip_serializing_if = "Option::is_none")]
1107 message: Option<String>,
1108 },
1109 ContainsLowercase {
1111 #[serde(default, skip_serializing_if = "Option::is_none")]
1113 message: Option<String>,
1114 },
1115 ContainsDigit {
1117 #[serde(default, skip_serializing_if = "Option::is_none")]
1119 message: Option<String>,
1120 },
1121 ContainsSpecial {
1123 #[serde(default, skip_serializing_if = "Option::is_none")]
1125 message: Option<String>,
1126 },
1127 MatchesField {
1129 field: String,
1131 #[serde(default, skip_serializing_if = "Option::is_none")]
1133 message: Option<String>,
1134 },
1135}
1136
1137impl ValidationRule {
1138 #[must_use]
1140 pub fn required() -> Self {
1141 Self::Required { message: None }
1142 }
1143
1144 #[must_use]
1146 pub fn min_length(value: usize) -> Self {
1147 Self::MinLength {
1148 value,
1149 message: None,
1150 }
1151 }
1152
1153 #[must_use]
1155 pub fn max_length(value: usize) -> Self {
1156 Self::MaxLength {
1157 value,
1158 message: None,
1159 }
1160 }
1161
1162 #[must_use]
1164 pub fn pattern(pattern: impl Into<String>) -> Self {
1165 Self::Pattern {
1166 pattern: pattern.into(),
1167 message: None,
1168 }
1169 }
1170
1171 #[must_use]
1173 pub fn email() -> Self {
1174 Self::Email { message: None }
1175 }
1176
1177 #[must_use]
1179 pub fn url() -> Self {
1180 Self::Url { message: None }
1181 }
1182
1183 #[must_use]
1185 pub fn contains_uppercase() -> Self {
1186 Self::ContainsUppercase { message: None }
1187 }
1188
1189 #[must_use]
1191 pub fn contains_lowercase() -> Self {
1192 Self::ContainsLowercase { message: None }
1193 }
1194
1195 #[must_use]
1197 pub fn contains_digit() -> Self {
1198 Self::ContainsDigit { message: None }
1199 }
1200
1201 #[must_use]
1203 pub fn contains_special() -> Self {
1204 Self::ContainsSpecial { message: None }
1205 }
1206
1207 #[must_use]
1209 pub fn matches_field(field: impl Into<String>) -> Self {
1210 Self::MatchesField {
1211 field: field.into(),
1212 message: None,
1213 }
1214 }
1215}
1216
1217#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1219#[serde(rename_all = "camelCase")]
1220pub struct FormData {
1221 pub values: HashMap<String, Value>,
1223
1224 #[serde(default)]
1226 pub submitted: bool,
1227
1228 #[serde(default, skip_serializing_if = "Option::is_none")]
1230 pub last_modified: Option<DateTime<Utc>>,
1231
1232 #[serde(default, skip_serializing_if = "Option::is_none")]
1234 pub submitted_by: Option<String>,
1235
1236 #[serde(default, skip_serializing_if = "Option::is_none")]
1238 pub submitted_at: Option<DateTime<Utc>>,
1239}
1240
1241impl FormData {
1242 #[must_use]
1244 pub fn new() -> Self {
1245 Self {
1246 values: HashMap::new(),
1247 submitted: false,
1248 last_modified: None,
1249 submitted_by: None,
1250 submitted_at: None,
1251 }
1252 }
1253
1254 pub fn set(&mut self, field_id: impl Into<String>, value: Value) {
1256 self.values.insert(field_id.into(), value);
1257 self.last_modified = Some(Utc::now());
1258 }
1259
1260 #[must_use]
1262 pub fn get(&self, field_id: &str) -> Option<&Value> {
1263 self.values.get(field_id)
1264 }
1265
1266 #[must_use]
1268 pub fn get_string(&self, field_id: &str) -> Option<&str> {
1269 self.values.get(field_id).and_then(Value::as_str)
1270 }
1271
1272 #[must_use]
1274 pub fn get_bool(&self, field_id: &str) -> Option<bool> {
1275 self.values.get(field_id).and_then(Value::as_bool)
1276 }
1277
1278 pub fn submit(&mut self, by: Option<String>) {
1280 self.submitted = true;
1281 self.submitted_by = by;
1282 self.submitted_at = Some(Utc::now());
1283 }
1284}
1285
1286impl Default for FormData {
1287 fn default() -> Self {
1288 Self::new()
1289 }
1290}
1291
1292#[cfg(test)]
1293mod tests {
1294 use super::*;
1295 use serde_json::json;
1296
1297 #[test]
1298 fn test_text_input_field() {
1299 let field = TextInputField::new("Full Name")
1300 .with_id("name")
1301 .with_placeholder("Enter your name")
1302 .required();
1303
1304 assert_eq!(field.label, "Full Name");
1305 assert_eq!(field.id, Some("name".to_string()));
1306 assert!(field.required);
1307 }
1308
1309 #[test]
1310 fn test_text_input_serialization() {
1311 let field = TextInputField::new("Email")
1312 .with_id("email")
1313 .with_input_type("email")
1314 .with_validation(FormValidation::with_rule(ValidationRule::email()));
1315
1316 let json = serde_json::to_string_pretty(&field).unwrap();
1317 assert!(json.contains("\"label\": \"Email\""));
1318 assert!(json.contains("\"inputType\": \"email\""));
1319 assert!(json.contains("\"type\": \"email\""));
1320 }
1321
1322 #[test]
1323 fn test_checkbox_field() {
1324 let field = CheckboxField::new("I agree to the terms")
1325 .with_id("terms")
1326 .required();
1327
1328 assert_eq!(field.label, "I agree to the terms");
1329 assert!(field.required);
1330 assert!(!field.default_checked);
1331 }
1332
1333 #[test]
1334 fn test_radio_group() {
1335 let options = vec![
1336 RadioOption::new("sm", "Small"),
1337 RadioOption::new("md", "Medium"),
1338 RadioOption::new("lg", "Large"),
1339 ];
1340
1341 let field = RadioGroupField::new("Size", options)
1342 .with_id("size")
1343 .with_default("md");
1344
1345 assert_eq!(field.options.len(), 3);
1346 assert_eq!(field.default_value, Some("md".to_string()));
1347 }
1348
1349 #[test]
1350 fn test_dropdown_with_groups() {
1351 let options = vec![
1352 DropdownOption::new("us", "United States").with_group("North America"),
1353 DropdownOption::new("ca", "Canada").with_group("North America"),
1354 DropdownOption::new("uk", "United Kingdom").with_group("Europe"),
1355 ];
1356
1357 let field = DropdownField::new("Country", options)
1358 .with_placeholder("Select a country")
1359 .required();
1360
1361 assert_eq!(field.options.len(), 3);
1362 assert_eq!(field.options[0].group, Some("North America".to_string()));
1363 }
1364
1365 #[test]
1366 fn test_date_picker() {
1367 let field = DatePickerField::new("Appointment Date")
1368 .with_id("date")
1369 .with_mode(DatePickerMode::Datetime)
1370 .with_min("2024-01-01")
1371 .required();
1372
1373 assert_eq!(field.mode, DatePickerMode::Datetime);
1374 assert_eq!(field.min, Some("2024-01-01".to_string()));
1375 }
1376
1377 #[test]
1378 fn test_signature_field() {
1379 let field = SignatureField::new("Signature")
1380 .with_id("sig")
1381 .with_legal_text("By signing, you agree to...")
1382 .required();
1383
1384 assert!(field.required);
1385 assert!(field.legal_text.is_some());
1386 }
1387
1388 #[test]
1389 fn test_validation_rules() {
1390 let validation = FormValidation::new(vec![
1391 ValidationRule::required(),
1392 ValidationRule::min_length(2),
1393 ValidationRule::max_length(50),
1394 ValidationRule::pattern(r"^[A-Za-z\s]+$"),
1395 ]);
1396
1397 assert_eq!(validation.rules.len(), 4);
1398 }
1399
1400 #[test]
1401 fn test_form_data() {
1402 let mut data = FormData::new();
1403 data.set("name", json!("John Doe"));
1404 data.set("age", json!(30));
1405 data.set("active", json!(true));
1406
1407 assert_eq!(data.get_string("name"), Some("John Doe"));
1408 assert_eq!(data.get_bool("active"), Some(true));
1409 assert!(!data.submitted);
1410
1411 data.submit(Some("user@example.com".to_string()));
1412 assert!(data.submitted);
1413 assert_eq!(data.submitted_by, Some("user@example.com".to_string()));
1414 }
1415
1416 #[test]
1417 fn test_form_field_enum() {
1418 let text_input = FormField::TextInput(TextInputField::new("Name").required());
1419
1420 assert!(text_input.is_required());
1421 assert_eq!(text_input.label(), "Name");
1422 }
1423
1424 #[test]
1425 fn test_form_field_serialization() {
1426 let field = FormField::TextInput(TextInputField::new("Name").with_id("name"));
1427 let json = serde_json::to_string(&field).unwrap();
1428 assert!(json.contains("\"fieldType\":\"textInput\""));
1429 }
1430
1431 #[test]
1432 fn test_declarative_validation_rules() {
1433 let uppercase = ValidationRule::contains_uppercase();
1435 let lowercase = ValidationRule::contains_lowercase();
1436 let digit = ValidationRule::contains_digit();
1437 let special = ValidationRule::contains_special();
1438 let matches = ValidationRule::matches_field("password");
1439
1440 let json = serde_json::to_string(&uppercase).unwrap();
1442 assert!(json.contains("\"type\":\"containsUppercase\""));
1443
1444 let json = serde_json::to_string(&lowercase).unwrap();
1445 assert!(json.contains("\"type\":\"containsLowercase\""));
1446
1447 let json = serde_json::to_string(&digit).unwrap();
1448 assert!(json.contains("\"type\":\"containsDigit\""));
1449
1450 let json = serde_json::to_string(&special).unwrap();
1451 assert!(json.contains("\"type\":\"containsSpecial\""));
1452
1453 let json = serde_json::to_string(&matches).unwrap();
1454 assert!(json.contains("\"type\":\"matchesField\""));
1455 assert!(json.contains("\"field\":\"password\""));
1456 }
1457
1458 #[test]
1459 fn test_matches_field_with_message() {
1460 let rule = ValidationRule::MatchesField {
1461 field: "confirm_password".to_string(),
1462 message: Some("Passwords must match".to_string()),
1463 };
1464
1465 let json = serde_json::to_string(&rule).unwrap();
1466 assert!(json.contains("\"field\":\"confirm_password\""));
1467 assert!(json.contains("\"message\":\"Passwords must match\""));
1468
1469 let parsed: ValidationRule = serde_json::from_str(&json).unwrap();
1471 assert_eq!(parsed, rule);
1472 }
1473
1474 #[test]
1475 fn test_conditional_validation_equals() {
1476 let cv = ConditionalValidation::new(
1477 Condition::equals("contact_method", json!("email")),
1478 ConditionalAction::require(),
1479 );
1480
1481 let json = serde_json::to_string_pretty(&cv).unwrap();
1482 assert!(json.contains("\"field\": \"contact_method\""));
1483 assert!(json.contains("\"equals\": \"email\""));
1484 assert!(json.contains("\"required\": true"));
1485
1486 let parsed: ConditionalValidation = serde_json::from_str(&json).unwrap();
1487 assert_eq!(parsed, cv);
1488 }
1489
1490 #[test]
1491 fn test_conditional_validation_not_equals() {
1492 let cv = ConditionalValidation::new(
1493 Condition::not_equals("status", json!("inactive")),
1494 ConditionalAction::with_validation(FormValidation::with_rule(
1495 ValidationRule::required(),
1496 )),
1497 );
1498
1499 let json = serde_json::to_string(&cv).unwrap();
1500 assert!(json.contains("\"notEquals\":\"inactive\""));
1501
1502 let parsed: ConditionalValidation = serde_json::from_str(&json).unwrap();
1503 assert_eq!(parsed, cv);
1504 }
1505
1506 #[test]
1507 fn test_conditional_validation_is_empty() {
1508 let cv = ConditionalValidation::new(
1509 Condition::is_empty("other_field"),
1510 ConditionalAction::require(),
1511 );
1512
1513 let json = serde_json::to_string(&cv).unwrap();
1514 assert!(json.contains("\"isEmpty\":true"));
1515
1516 let parsed: ConditionalValidation = serde_json::from_str(&json).unwrap();
1517 assert_eq!(parsed, cv);
1518 }
1519
1520 #[test]
1521 fn test_conditional_validation_is_not_empty() {
1522 let cv = ConditionalValidation::new(
1523 Condition::is_not_empty("parent_field"),
1524 ConditionalAction::require_with_validation(FormValidation::with_rule(
1525 ValidationRule::min_length(3),
1526 )),
1527 );
1528
1529 let json = serde_json::to_string(&cv).unwrap();
1530 assert!(json.contains("\"isNotEmpty\":true"));
1531 assert!(json.contains("\"required\":true"));
1532 assert!(json.contains("\"minLength\""));
1533
1534 let parsed: ConditionalValidation = serde_json::from_str(&json).unwrap();
1535 assert_eq!(parsed, cv);
1536 }
1537
1538 #[test]
1539 fn test_field_with_conditional_validation() {
1540 let cv = ConditionalValidation::new(
1541 Condition::equals("contact_method", json!("email")),
1542 ConditionalAction::require(),
1543 );
1544
1545 let field = TextInputField::new("Email Address")
1546 .with_id("email")
1547 .with_conditional_validation(cv);
1548
1549 assert!(field.conditional_validation.is_some());
1550
1551 let json = serde_json::to_string_pretty(&field).unwrap();
1552 assert!(json.contains("\"conditionalValidation\""));
1553 assert!(json.contains("\"contact_method\""));
1554
1555 let parsed: TextInputField = serde_json::from_str(&json).unwrap();
1556 assert_eq!(parsed, field);
1557 }
1558
1559 #[test]
1560 fn test_backward_compat_no_conditional_validation() {
1561 let json = r#"{
1563 "label": "Name",
1564 "required": true
1565 }"#;
1566
1567 let field: TextInputField = serde_json::from_str(json).unwrap();
1568 assert_eq!(field.label, "Name");
1569 assert!(field.required);
1570 assert!(field.conditional_validation.is_none());
1571 }
1572
1573 #[test]
1574 fn test_conditional_validation_on_checkbox() {
1575 let cv = ConditionalValidation::new(
1576 Condition::equals("has_address", json!(true)),
1577 ConditionalAction::require(),
1578 );
1579
1580 let field = CheckboxField::new("Confirm address").with_conditional_validation(cv);
1581
1582 let json = serde_json::to_string(&field).unwrap();
1583 let parsed: CheckboxField = serde_json::from_str(&json).unwrap();
1584 assert_eq!(parsed, field);
1585 }
1586
1587 #[test]
1588 fn test_conditional_validation_skipped_when_none() {
1589 let field = TextInputField::new("Name");
1591 let json = serde_json::to_string(&field).unwrap();
1592 assert!(!json.contains("conditionalValidation"));
1593 }
1594}