Skip to main content

cdx_core/extensions/
forms.rs

1//! Forms extension for interactive form fields.
2//!
3//! This extension provides form field types for creating interactive documents
4//! with data collection capabilities.
5//!
6//! # Supported Field Types
7//!
8//! - `forms:textInput` - Single-line text input
9//! - `forms:textArea` - Multi-line text input
10//! - `forms:checkbox` - Boolean checkbox
11//! - `forms:radioGroup` - Single selection from options
12//! - `forms:dropdown` - Dropdown selection
13//! - `forms:datePicker` - Date/time selection
14//! - `forms:signature` - Digital signature capture
15//!
16//! # Example
17//!
18//! ```json
19//! {
20//!   "type": "forms:textInput",
21//!   "id": "email",
22//!   "label": "Email Address",
23//!   "placeholder": "you@example.com",
24//!   "required": true,
25//!   "validation": {
26//!     "rules": [{"type": "email"}]
27//!   }
28//! }
29//! ```
30
31use std::collections::HashMap;
32
33use chrono::{DateTime, Utc};
34use serde::{Deserialize, Serialize};
35use serde_json::Value;
36
37use super::ExtensionBlock;
38
39/// A form field that can appear in a Codex document.
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41#[serde(tag = "fieldType", rename_all = "camelCase")]
42pub enum FormField {
43    /// Single-line text input.
44    TextInput(TextInputField),
45    /// Multi-line text input.
46    TextArea(TextAreaField),
47    /// Boolean checkbox.
48    Checkbox(CheckboxField),
49    /// Single selection from radio options.
50    RadioGroup(RadioGroupField),
51    /// Dropdown selection.
52    Dropdown(DropdownField),
53    /// Date/time picker.
54    DatePicker(DatePickerField),
55    /// Digital signature capture.
56    Signature(SignatureField),
57}
58
59impl FormField {
60    /// Try to convert an extension block to a form field.
61    #[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    /// Get the field ID.
94    #[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    /// Get the field label.
108    #[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    /// Check if the field is required.
122    #[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    /// Get the field's validation rules.
136    #[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/// Single-line text input field.
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct TextInputField {
153    /// Optional unique identifier.
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub id: Option<String>,
156
157    /// Field label displayed to the user.
158    pub label: String,
159
160    /// Placeholder text when empty.
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub placeholder: Option<String>,
163
164    /// Default value.
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub default_value: Option<String>,
167
168    /// Whether the field is required.
169    #[serde(default)]
170    pub required: bool,
171
172    /// Whether the field is read-only.
173    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
174    pub readonly: bool,
175
176    /// Input type hint (text, email, url, tel, password).
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub input_type: Option<String>,
179
180    /// Validation rules.
181    #[serde(default, skip_serializing_if = "Option::is_none")]
182    pub validation: Option<FormValidation>,
183
184    /// Conditional validation based on another field's value.
185    #[serde(default, skip_serializing_if = "Option::is_none")]
186    pub conditional_validation: Option<ConditionalValidation>,
187}
188
189impl TextInputField {
190    /// Create a new text input field.
191    #[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    /// Set the field ID.
207    #[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    /// Set the placeholder text.
214    #[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    /// Set the default value.
221    #[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    /// Mark as required.
228    #[must_use]
229    pub const fn required(mut self) -> Self {
230        self.required = true;
231        self
232    }
233
234    /// Mark as read-only.
235    #[must_use]
236    pub const fn readonly(mut self) -> Self {
237        self.readonly = true;
238        self
239    }
240
241    /// Set the input type.
242    #[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    /// Set validation rules.
249    #[must_use]
250    pub fn with_validation(mut self, validation: FormValidation) -> Self {
251        self.validation = Some(validation);
252        self
253    }
254
255    /// Set conditional validation.
256    #[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/// Multi-line text area field.
264#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct TextAreaField {
267    /// Optional unique identifier.
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    pub id: Option<String>,
270
271    /// Field label displayed to the user.
272    pub label: String,
273
274    /// Placeholder text when empty.
275    #[serde(default, skip_serializing_if = "Option::is_none")]
276    pub placeholder: Option<String>,
277
278    /// Default value.
279    #[serde(default, skip_serializing_if = "Option::is_none")]
280    pub default_value: Option<String>,
281
282    /// Whether the field is required.
283    #[serde(default)]
284    pub required: bool,
285
286    /// Whether the field is read-only.
287    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
288    pub readonly: bool,
289
290    /// Number of visible rows.
291    #[serde(default, skip_serializing_if = "Option::is_none")]
292    pub rows: Option<u32>,
293
294    /// Maximum character length.
295    #[serde(default, skip_serializing_if = "Option::is_none")]
296    pub max_length: Option<usize>,
297
298    /// Validation rules.
299    #[serde(default, skip_serializing_if = "Option::is_none")]
300    pub validation: Option<FormValidation>,
301
302    /// Conditional validation based on another field's value.
303    #[serde(default, skip_serializing_if = "Option::is_none")]
304    pub conditional_validation: Option<ConditionalValidation>,
305}
306
307impl TextAreaField {
308    /// Create a new text area field.
309    #[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    /// Set the field ID.
326    #[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    /// Set the number of rows.
333    #[must_use]
334    pub const fn with_rows(mut self, rows: u32) -> Self {
335        self.rows = Some(rows);
336        self
337    }
338
339    /// Set the maximum length.
340    #[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    /// Mark as required.
347    #[must_use]
348    pub const fn required(mut self) -> Self {
349        self.required = true;
350        self
351    }
352
353    /// Set conditional validation.
354    #[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/// Boolean checkbox field.
362#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
363#[serde(rename_all = "camelCase")]
364pub struct CheckboxField {
365    /// Optional unique identifier.
366    #[serde(default, skip_serializing_if = "Option::is_none")]
367    pub id: Option<String>,
368
369    /// Field label displayed to the user.
370    pub label: String,
371
372    /// Default checked state.
373    #[serde(default)]
374    pub default_checked: bool,
375
376    /// Whether the field is required (must be checked).
377    #[serde(default)]
378    pub required: bool,
379
380    /// Whether the field is read-only.
381    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
382    pub readonly: bool,
383
384    /// Conditional validation based on another field's value.
385    #[serde(default, skip_serializing_if = "Option::is_none")]
386    pub conditional_validation: Option<ConditionalValidation>,
387}
388
389impl CheckboxField {
390    /// Create a new checkbox field.
391    #[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    /// Set the field ID.
404    #[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    /// Set default checked state.
411    #[must_use]
412    pub const fn checked(mut self) -> Self {
413        self.default_checked = true;
414        self
415    }
416
417    /// Mark as required.
418    #[must_use]
419    pub const fn required(mut self) -> Self {
420        self.required = true;
421        self
422    }
423
424    /// Set conditional validation.
425    #[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/// Radio button option.
433#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct RadioOption {
436    /// Option value (submitted value).
437    pub value: String,
438
439    /// Display label.
440    pub label: String,
441
442    /// Whether this option is disabled.
443    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
444    pub disabled: bool,
445}
446
447impl RadioOption {
448    /// Create a new radio option.
449    #[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/// Radio button group for single selection.
460#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct RadioGroupField {
463    /// Optional unique identifier.
464    #[serde(default, skip_serializing_if = "Option::is_none")]
465    pub id: Option<String>,
466
467    /// Field label displayed to the user.
468    pub label: String,
469
470    /// Available options.
471    pub options: Vec<RadioOption>,
472
473    /// Default selected value.
474    #[serde(default, skip_serializing_if = "Option::is_none")]
475    pub default_value: Option<String>,
476
477    /// Whether a selection is required.
478    #[serde(default)]
479    pub required: bool,
480
481    /// Whether the field is read-only.
482    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
483    pub readonly: bool,
484
485    /// Conditional validation based on another field's value.
486    #[serde(default, skip_serializing_if = "Option::is_none")]
487    pub conditional_validation: Option<ConditionalValidation>,
488}
489
490impl RadioGroupField {
491    /// Create a new radio group field.
492    #[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    /// Set the field ID.
506    #[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    /// Set the default value.
513    #[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    /// Mark as required.
520    #[must_use]
521    pub const fn required(mut self) -> Self {
522        self.required = true;
523        self
524    }
525
526    /// Set conditional validation.
527    #[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/// Dropdown option.
535#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
536#[serde(rename_all = "camelCase")]
537pub struct DropdownOption {
538    /// Option value (submitted value).
539    pub value: String,
540
541    /// Display label.
542    pub label: String,
543
544    /// Whether this option is disabled.
545    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
546    pub disabled: bool,
547
548    /// Option group (for grouping options).
549    #[serde(default, skip_serializing_if = "Option::is_none")]
550    pub group: Option<String>,
551}
552
553impl DropdownOption {
554    /// Create a new dropdown option.
555    #[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    /// Set the option group.
566    #[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/// Dropdown selection field.
574#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
575#[serde(rename_all = "camelCase")]
576pub struct DropdownField {
577    /// Optional unique identifier.
578    #[serde(default, skip_serializing_if = "Option::is_none")]
579    pub id: Option<String>,
580
581    /// Field label displayed to the user.
582    pub label: String,
583
584    /// Available options.
585    pub options: Vec<DropdownOption>,
586
587    /// Default selected value.
588    #[serde(default, skip_serializing_if = "Option::is_none")]
589    pub default_value: Option<String>,
590
591    /// Placeholder text when no selection.
592    #[serde(default, skip_serializing_if = "Option::is_none")]
593    pub placeholder: Option<String>,
594
595    /// Whether a selection is required.
596    #[serde(default)]
597    pub required: bool,
598
599    /// Whether the field is read-only.
600    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
601    pub readonly: bool,
602
603    /// Allow multiple selections.
604    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
605    pub multiple: bool,
606
607    /// Conditional validation based on another field's value.
608    #[serde(default, skip_serializing_if = "Option::is_none")]
609    pub conditional_validation: Option<ConditionalValidation>,
610}
611
612impl DropdownField {
613    /// Create a new dropdown field.
614    #[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    /// Set the field ID.
630    #[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    /// Set the placeholder.
637    #[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    /// Set the default value.
644    #[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    /// Mark as required.
651    #[must_use]
652    pub const fn required(mut self) -> Self {
653        self.required = true;
654        self
655    }
656
657    /// Allow multiple selections.
658    #[must_use]
659    pub const fn multiple(mut self) -> Self {
660        self.multiple = true;
661        self
662    }
663
664    /// Set conditional validation.
665    #[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/// Date/time picker field.
673#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
674#[serde(rename_all = "camelCase")]
675pub struct DatePickerField {
676    /// Optional unique identifier.
677    #[serde(default, skip_serializing_if = "Option::is_none")]
678    pub id: Option<String>,
679
680    /// Field label displayed to the user.
681    pub label: String,
682
683    /// Picker mode (date, time, datetime).
684    #[serde(default)]
685    pub mode: DatePickerMode,
686
687    /// Default value (ISO 8601 format).
688    #[serde(default, skip_serializing_if = "Option::is_none")]
689    pub default_value: Option<String>,
690
691    /// Minimum date/time.
692    #[serde(default, skip_serializing_if = "Option::is_none")]
693    pub min: Option<String>,
694
695    /// Maximum date/time.
696    #[serde(default, skip_serializing_if = "Option::is_none")]
697    pub max: Option<String>,
698
699    /// Whether the field is required.
700    #[serde(default)]
701    pub required: bool,
702
703    /// Whether the field is read-only.
704    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
705    pub readonly: bool,
706
707    /// Validation rules.
708    #[serde(default, skip_serializing_if = "Option::is_none")]
709    pub validation: Option<FormValidation>,
710
711    /// Conditional validation based on another field's value.
712    #[serde(default, skip_serializing_if = "Option::is_none")]
713    pub conditional_validation: Option<ConditionalValidation>,
714}
715
716/// Date picker mode.
717#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
718#[serde(rename_all = "lowercase")]
719pub enum DatePickerMode {
720    /// Date only (YYYY-MM-DD).
721    #[default]
722    Date,
723    /// Time only (HH:MM:SS).
724    Time,
725    /// Date and time.
726    Datetime,
727}
728
729impl DatePickerField {
730    /// Create a new date picker field.
731    #[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    /// Set the field ID.
748    #[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    /// Set the picker mode.
755    #[must_use]
756    pub const fn with_mode(mut self, mode: DatePickerMode) -> Self {
757        self.mode = mode;
758        self
759    }
760
761    /// Set the minimum date.
762    #[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    /// Set the maximum date.
769    #[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    /// Mark as required.
776    #[must_use]
777    pub const fn required(mut self) -> Self {
778        self.required = true;
779        self
780    }
781
782    /// Set conditional validation.
783    #[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/// Digital signature capture field.
791#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
792#[serde(rename_all = "camelCase")]
793pub struct SignatureField {
794    /// Optional unique identifier.
795    #[serde(default, skip_serializing_if = "Option::is_none")]
796    pub id: Option<String>,
797
798    /// Field label displayed to the user.
799    pub label: String,
800
801    /// Whether the signature is required.
802    #[serde(default)]
803    pub required: bool,
804
805    /// Whether the field is read-only.
806    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
807    pub readonly: bool,
808
809    /// Legal text that must be agreed to before signing.
810    #[serde(default, skip_serializing_if = "Option::is_none")]
811    pub legal_text: Option<String>,
812
813    /// Conditional validation based on another field's value.
814    #[serde(default, skip_serializing_if = "Option::is_none")]
815    pub conditional_validation: Option<ConditionalValidation>,
816}
817
818impl SignatureField {
819    /// Create a new signature field.
820    #[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    /// Set the field ID.
833    #[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    /// Set the legal text.
840    #[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    /// Mark as required.
847    #[must_use]
848    pub const fn required(mut self) -> Self {
849        self.required = true;
850        self
851    }
852
853    /// Set conditional validation.
854    #[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/// Form validation configuration.
862#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
863#[serde(rename_all = "camelCase")]
864pub struct FormValidation {
865    /// Validation rules to apply.
866    pub rules: Vec<ValidationRule>,
867}
868
869impl FormValidation {
870    /// Create a new validation configuration.
871    #[must_use]
872    pub fn new(rules: Vec<ValidationRule>) -> Self {
873        Self { rules }
874    }
875
876    /// Create validation with a single rule.
877    #[must_use]
878    pub fn with_rule(rule: ValidationRule) -> Self {
879        Self { rules: vec![rule] }
880    }
881}
882
883/// Conditional validation that applies rules based on another field's value.
884///
885/// This allows form fields to have validation that only applies when
886/// certain conditions are met (e.g., "require email if contact method is email").
887#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
888#[serde(rename_all = "camelCase")]
889pub struct ConditionalValidation {
890    /// The condition that triggers the validation.
891    pub when: Condition,
892
893    /// The action to take when the condition is met.
894    pub then: ConditionalAction,
895}
896
897impl ConditionalValidation {
898    /// Create a new conditional validation.
899    #[must_use]
900    pub fn new(when: Condition, then: ConditionalAction) -> Self {
901        Self { when, then }
902    }
903}
904
905/// A condition that references another field's value.
906#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
907#[serde(rename_all = "camelCase")]
908pub struct Condition {
909    /// The ID of the field to check.
910    pub field: String,
911
912    /// The comparison operator and expected value.
913    #[serde(flatten)]
914    pub operator: ConditionOperator,
915}
916
917impl Condition {
918    /// Create an "equals" condition.
919    #[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    /// Create a "not equals" condition.
933    #[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    /// Create an "is empty" condition.
947    #[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    /// Create an "is not empty" condition.
961    #[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/// The comparison operator for a conditional validation.
976///
977/// Exactly one field should be set. When serialized with `#[serde(flatten)]`
978/// on the parent, this produces `{"equals": value}` or `{"isNotEmpty": true}`.
979#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
980#[serde(rename_all = "camelCase")]
981pub struct ConditionOperator {
982    /// Field value equals the specified value.
983    #[serde(default, skip_serializing_if = "Option::is_none")]
984    pub equals: Option<Value>,
985
986    /// Field value does not equal the specified value.
987    #[serde(default, skip_serializing_if = "Option::is_none")]
988    pub not_equals: Option<Value>,
989
990    /// Field value is empty (null, empty string, or missing).
991    #[serde(default, skip_serializing_if = "Option::is_none")]
992    pub is_empty: Option<bool>,
993
994    /// Field value is not empty.
995    #[serde(default, skip_serializing_if = "Option::is_none")]
996    pub is_not_empty: Option<bool>,
997}
998
999/// The action to apply when a condition is met.
1000#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1001#[serde(rename_all = "camelCase")]
1002pub struct ConditionalAction {
1003    /// Override whether the field is required.
1004    #[serde(default, skip_serializing_if = "Option::is_none")]
1005    pub required: Option<bool>,
1006
1007    /// Additional validation rules to apply.
1008    #[serde(default, skip_serializing_if = "Option::is_none")]
1009    pub validation: Option<FormValidation>,
1010}
1011
1012impl ConditionalAction {
1013    /// Create an action that makes the field required.
1014    #[must_use]
1015    pub fn require() -> Self {
1016        Self {
1017            required: Some(true),
1018            validation: None,
1019        }
1020    }
1021
1022    /// Create an action with specific validation rules.
1023    #[must_use]
1024    pub fn with_validation(validation: FormValidation) -> Self {
1025        Self {
1026            required: None,
1027            validation: Some(validation),
1028        }
1029    }
1030
1031    /// Create an action that makes the field required and adds validation.
1032    #[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/// A validation rule for form fields.
1042#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1043#[serde(tag = "type", rename_all = "camelCase")]
1044pub enum ValidationRule {
1045    /// Field is required.
1046    Required {
1047        /// Custom error message.
1048        #[serde(default, skip_serializing_if = "Option::is_none")]
1049        message: Option<String>,
1050    },
1051    /// Minimum length.
1052    MinLength {
1053        /// Minimum character count.
1054        value: usize,
1055        /// Custom error message.
1056        #[serde(default, skip_serializing_if = "Option::is_none")]
1057        message: Option<String>,
1058    },
1059    /// Maximum length.
1060    MaxLength {
1061        /// Maximum character count.
1062        value: usize,
1063        /// Custom error message.
1064        #[serde(default, skip_serializing_if = "Option::is_none")]
1065        message: Option<String>,
1066    },
1067    /// Regular expression pattern.
1068    Pattern {
1069        /// Regex pattern.
1070        pattern: String,
1071        /// Custom error message.
1072        #[serde(default, skip_serializing_if = "Option::is_none")]
1073        message: Option<String>,
1074    },
1075    /// Email format.
1076    Email {
1077        /// Custom error message.
1078        #[serde(default, skip_serializing_if = "Option::is_none")]
1079        message: Option<String>,
1080    },
1081    /// URL format.
1082    Url {
1083        /// Custom error message.
1084        #[serde(default, skip_serializing_if = "Option::is_none")]
1085        message: Option<String>,
1086    },
1087    /// Minimum numeric value.
1088    Min {
1089        /// Minimum value.
1090        value: i64,
1091        /// Custom error message.
1092        #[serde(default, skip_serializing_if = "Option::is_none")]
1093        message: Option<String>,
1094    },
1095    /// Maximum numeric value.
1096    Max {
1097        /// Maximum value.
1098        value: i64,
1099        /// Custom error message.
1100        #[serde(default, skip_serializing_if = "Option::is_none")]
1101        message: Option<String>,
1102    },
1103    /// Requires at least one uppercase letter.
1104    ContainsUppercase {
1105        /// Custom error message.
1106        #[serde(default, skip_serializing_if = "Option::is_none")]
1107        message: Option<String>,
1108    },
1109    /// Requires at least one lowercase letter.
1110    ContainsLowercase {
1111        /// Custom error message.
1112        #[serde(default, skip_serializing_if = "Option::is_none")]
1113        message: Option<String>,
1114    },
1115    /// Requires at least one digit.
1116    ContainsDigit {
1117        /// Custom error message.
1118        #[serde(default, skip_serializing_if = "Option::is_none")]
1119        message: Option<String>,
1120    },
1121    /// Requires at least one special character.
1122    ContainsSpecial {
1123        /// Custom error message.
1124        #[serde(default, skip_serializing_if = "Option::is_none")]
1125        message: Option<String>,
1126    },
1127    /// Value must match another field.
1128    MatchesField {
1129        /// The field name that this value must match.
1130        field: String,
1131        /// Custom error message.
1132        #[serde(default, skip_serializing_if = "Option::is_none")]
1133        message: Option<String>,
1134    },
1135}
1136
1137impl ValidationRule {
1138    /// Create a required rule.
1139    #[must_use]
1140    pub fn required() -> Self {
1141        Self::Required { message: None }
1142    }
1143
1144    /// Create a min length rule.
1145    #[must_use]
1146    pub fn min_length(value: usize) -> Self {
1147        Self::MinLength {
1148            value,
1149            message: None,
1150        }
1151    }
1152
1153    /// Create a max length rule.
1154    #[must_use]
1155    pub fn max_length(value: usize) -> Self {
1156        Self::MaxLength {
1157            value,
1158            message: None,
1159        }
1160    }
1161
1162    /// Create a pattern rule.
1163    #[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    /// Create an email rule.
1172    #[must_use]
1173    pub fn email() -> Self {
1174        Self::Email { message: None }
1175    }
1176
1177    /// Create a URL rule.
1178    #[must_use]
1179    pub fn url() -> Self {
1180        Self::Url { message: None }
1181    }
1182
1183    /// Create a contains uppercase rule.
1184    #[must_use]
1185    pub fn contains_uppercase() -> Self {
1186        Self::ContainsUppercase { message: None }
1187    }
1188
1189    /// Create a contains lowercase rule.
1190    #[must_use]
1191    pub fn contains_lowercase() -> Self {
1192        Self::ContainsLowercase { message: None }
1193    }
1194
1195    /// Create a contains digit rule.
1196    #[must_use]
1197    pub fn contains_digit() -> Self {
1198        Self::ContainsDigit { message: None }
1199    }
1200
1201    /// Create a contains special character rule.
1202    #[must_use]
1203    pub fn contains_special() -> Self {
1204        Self::ContainsSpecial { message: None }
1205    }
1206
1207    /// Create a matches field rule.
1208    #[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/// Form data submitted by users.
1218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1219#[serde(rename_all = "camelCase")]
1220pub struct FormData {
1221    /// Field values indexed by field ID.
1222    pub values: HashMap<String, Value>,
1223
1224    /// Whether the form has been submitted.
1225    #[serde(default)]
1226    pub submitted: bool,
1227
1228    /// When the form was last modified.
1229    #[serde(default, skip_serializing_if = "Option::is_none")]
1230    pub last_modified: Option<DateTime<Utc>>,
1231
1232    /// Submitter information.
1233    #[serde(default, skip_serializing_if = "Option::is_none")]
1234    pub submitted_by: Option<String>,
1235
1236    /// When the form was submitted.
1237    #[serde(default, skip_serializing_if = "Option::is_none")]
1238    pub submitted_at: Option<DateTime<Utc>>,
1239}
1240
1241impl FormData {
1242    /// Create new empty form data.
1243    #[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    /// Set a field value.
1255    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    /// Get a field value.
1261    #[must_use]
1262    pub fn get(&self, field_id: &str) -> Option<&Value> {
1263        self.values.get(field_id)
1264    }
1265
1266    /// Get a string value.
1267    #[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    /// Get a boolean value.
1273    #[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    /// Mark the form as submitted.
1279    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        // Test construction
1434        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        // Test serialization
1441        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        // Test roundtrip
1470        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        // Fields without conditionalValidation should deserialize fine
1562        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        // Ensure conditionalValidation is not present in JSON when None
1590        let field = TextInputField::new("Name");
1591        let json = serde_json::to_string(&field).unwrap();
1592        assert!(!json.contains("conditionalValidation"));
1593    }
1594}