Skip to main content

reinhardt_forms/
lib.rs

1#![warn(missing_docs)]
2
3//! # Reinhardt Forms
4//!
5//! Form processing and validation for the Reinhardt framework.
6//!
7//! ## Overview
8//!
9//! This crate provides comprehensive form processing capabilities inspired by Django's
10//! form system, focusing on data validation and multi-step form wizards.
11//!
12//! This crate is designed to be WASM-compatible, providing a pure form processing layer
13//! without HTML generation or platform-specific features.
14//!
15//! ## Features
16//!
17//! - **[`Form`]**: Base form class with validation
18//! - **[`ModelForm`]**: Auto-generated forms from model definitions
19//! - **[`FormSet`]**: Handle multiple forms of the same type
20//! - **[`FormWizard`]**: Multi-step form workflows
21//! - **Field Types**: 20+ field types (CharField, IntegerField, EmailField, etc.)
22//! - **WASM Support**: Compatible with WebAssembly targets via `wasm_compat` module
23//!
24//! ## Quick Start
25//!
26//! ### Basic Form
27//!
28//! ```rust,ignore
29//! use reinhardt_forms::{Form, CharField, EmailField, IntegerField};
30//!
31//! // Build a form imperatively using add_field()
32//! let mut form = Form::new();
33//! form.add_field(Box::new(CharField::new("name")));
34//! form.add_field(Box::new(EmailField::new("email")));
35//! form.add_field(Box::new(IntegerField::new("age")));
36//! form.add_field(Box::new(CharField::new("message")));
37//!
38//! // Validate form data
39//! form.bind(&request_data);
40//! if form.is_valid() {
41//!     // Process the validated form...
42//! } else {
43//!     let errors = form.errors();
44//! }
45//! ```
46//!
47//! ### Prefixed Form Data
48//!
49//! A prefixed form expects submitted field names to use the prefix. The
50//! validated values are exposed through canonical field names, while bound
51//! fields continue to read the original submitted values for rerendering.
52//!
53//! ```rust
54//! use reinhardt_forms::{CharField, Field, Form};
55//! use serde_json::json;
56//! use std::collections::HashMap;
57//!
58//! let mut form = Form::with_prefix("profile".to_string());
59//! form.add_field(Box::new(CharField::new("name".to_string()).required()));
60//! form.bind(HashMap::from([("profile-name".to_string(), json!("Ada"))]));
61//!
62//! assert!(form.is_valid());
63//! assert_eq!(form.cleaned_data().get("name"), Some(&json!("Ada")));
64//! assert_eq!(
65//!     form.get_bound_field("name").unwrap().value(),
66//!     Some(&json!("Ada"))
67//! );
68//! ```
69//!
70//! ### Model Form
71//!
72//! ```rust,ignore
73//! use reinhardt_forms::{ModelForm, ModelFormBuilder};
74//!
75//! // Auto-generate form from User model
76//! let form = ModelFormBuilder::<User>::new()
77//!     .fields(vec!["username".to_string(), "email".to_string(), "bio".to_string()])
78//!     .exclude(vec!["password".to_string()])
79//!     .build();
80//! ```
81//!
82//! ## Available Field Types
83//!
84//! | Field | Description |
85//! |-------|-------------|
86//! | [`CharField`] | Text input with max_length validation |
87//! | [`IntegerField`] | Integer input with min/max validation |
88//! | [`FloatField`] | Floating-point number input |
89//! | [`DecimalField`] | Decimal number with precision control |
90//! | [`BooleanField`] | Checkbox input |
91//! | [`EmailField`] | Email address validation |
92//! | [`URLField`] | URL validation |
93//! | [`DateField`] | Date input with format parsing |
94//! | [`DateTimeField`] | DateTime input |
95//! | [`TimeField`] | Time input |
96//! | [`DurationField`] | Duration input |
97//! | [`FileField`] | File upload |
98//! | [`ImageField`] | Image upload with dimension validation |
99//! | [`ChoiceField`] | Select dropdown |
100//! | [`MultipleChoiceField`] | Multi-select |
101//! | [`ModelChoiceField`] | Foreign key selection |
102//! | [`ModelMultipleChoiceField`] | Multiple model selection with normalized dirty-state comparison |
103//! | [`JSONField`] | JSON data input |
104//! | [`UUIDField`] | UUID input |
105//! | [`SlugField`] | URL-safe slug input |
106//! | [`RegexField`] | Custom regex validation |
107//!
108//! `ModelMultipleChoiceField` compares selected values without considering
109//! order when [`Form::has_changed`] runs. Numeric IDs and strings with the same
110//! textual representation are equivalent, while booleans, nulls, arrays, and
111//! objects remain distinct JSON types.
112//!
113//! ## FormSets
114//!
115//! Handle multiple forms of the same type:
116//!
117//! ```rust,ignore
118//! use reinhardt_forms::{FormSet, FormSetFactory};
119//!
120//! // Create a formset with 3 forms
121//! let formset = FormSetFactory::<ItemForm>::new()
122//!     .extra(3)
123//!     .min_num(1)
124//!     .max_num(10)
125//!     .build();
126//!
127//! if formset.is_valid() {
128//!     for form in formset.forms() {
129//!         // Process each form
130//!     }
131//! }
132//! ```
133//!
134//! ## Form Wizard
135//!
136//! Multi-step forms:
137//!
138//! ```rust,ignore
139//! use reinhardt_forms::{FormWizard, WizardStep};
140//!
141//! let wizard = FormWizard::new()
142//!     .add_step(WizardStep::new("account", AccountForm::new()))
143//!     .add_step(WizardStep::new("profile", ProfileForm::new()))
144//!     .add_step(WizardStep::new("confirmation", ConfirmForm::new()));
145//!
146//! // Process wizard step
147//! let result = wizard.process_step(&request).await?;
148//! ```
149
150/// Bound field rendering with data and errors attached.
151pub mod bound_field;
152/// Core form field trait and error types.
153pub mod field;
154/// Built-in field types (text, email, integer, choice, etc.).
155pub mod fields;
156/// Form trait and validation logic.
157pub mod form;
158/// Formset for managing multiple form instances.
159pub mod formset;
160/// Built-in formset types (inline, base).
161pub mod formsets;
162/// Model-backed form with automatic field generation.
163pub mod model_form;
164/// Model-backed formset for bulk editing.
165pub mod model_formset;
166/// Field-level and form-level validators.
167pub mod validators;
168/// WASM compatibility layer for client-side forms.
169pub mod wasm_compat;
170/// Multi-step form wizard.
171pub mod wizard;
172
173pub use bound_field::BoundField;
174pub use field::{
175	ErrorType,
176	FieldError,
177	FieldResult,
178	FormField as Field, // Alias for compatibility
179	FormField,
180	Widget,
181	escape_attribute,
182	html_escape,
183};
184pub use fields::{
185	BooleanField, CharField, ChoiceField, ColorField, ComboField, DateField, DateTimeField,
186	DecimalField, DurationField, EmailField, FileField, FloatField, GenericIPAddressField,
187	IPProtocol, ImageField, IntegerField, JSONField, ModelChoiceField, ModelMultipleChoiceField,
188	MultiValueField, MultipleChoiceField, PASSWORD_REDACTED, PasswordField, RegexField, SlugField,
189	SplitDateTimeField, TimeField, URLField, UUIDField,
190};
191pub use form::{Form, FormError, FormResult};
192pub use formset::FormSet;
193pub use formsets::{
194	FormSetFactory,
195	InlineFormSet,
196	ModelFormSet as AdvancedModelFormSet, // Renamed to avoid conflict
197};
198pub use model_form::{FieldType, FormModel, ModelForm, ModelFormBuilder, ModelFormConfig};
199pub use model_formset::{ModelFormSet, ModelFormSetBuilder, ModelFormSetConfig};
200pub use validators::{SlugValidator, UrlValidator};
201pub use wizard::{FormWizard, WizardStep};