1use regex::Regex;
7use std::cell::RefCell;
8use std::collections::HashMap;
9use std::rc::Rc;
10
11type ValidationFn = Rc<dyn Fn(&str) -> Option<String>>;
13
14#[derive(Clone)]
16pub enum Validator {
17 Required,
19 MinLength(usize),
21 MaxLength(usize),
23 Pattern(Regex),
25 Email,
27 Number,
29 Integer,
31 Custom(ValidationFn),
33}
34
35impl Validator {
36 pub fn custom<F>(f: F) -> Self
38 where
39 F: Fn(&str) -> Option<String> + 'static,
40 {
41 Validator::Custom(Rc::new(f))
42 }
43
44 pub fn pattern(pattern: &str) -> Result<Self, regex::Error> {
46 Ok(Validator::Pattern(Regex::new(pattern)?))
47 }
48
49 pub fn validate(&self, value: &str) -> Option<String> {
51 match self {
52 Validator::Required => {
53 if value.trim().is_empty() {
54 Some("This field is required".to_string())
55 } else {
56 None
57 }
58 }
59 Validator::MinLength(min) => {
60 if value.len() < *min {
61 Some(format!("Must be at least {} characters", min))
62 } else {
63 None
64 }
65 }
66 Validator::MaxLength(max) => {
67 if value.len() > *max {
68 Some(format!("Must be at most {} characters", max))
69 } else {
70 None
71 }
72 }
73 Validator::Pattern(regex) => {
74 if regex.is_match(value) {
75 None
76 } else {
77 Some("Invalid format".to_string())
78 }
79 }
80 Validator::Email => {
81 let email_pattern = Regex::new(r"^[^@\s]+@[^@\s]+\.[^@\s]+$").unwrap();
83 if value.is_empty() || email_pattern.is_match(value) {
84 None
85 } else {
86 Some("Invalid email address".to_string())
87 }
88 }
89 Validator::Number => {
90 if value.is_empty() || value.parse::<f64>().is_ok() {
91 None
92 } else {
93 Some("Must be a valid number".to_string())
94 }
95 }
96 Validator::Integer => {
97 if value.is_empty() || value.parse::<i64>().is_ok() {
98 None
99 } else {
100 Some("Must be a valid integer".to_string())
101 }
102 }
103 Validator::Custom(f) => f(value),
104 }
105 }
106}
107
108#[derive(Clone)]
110pub struct FormField {
111 pub name: String,
113 pub value: String,
115 pub validators: Vec<Validator>,
117 pub error: Option<String>,
119 pub custom_error_message: Option<String>,
121 pub touched: bool,
123}
124
125impl FormField {
126 pub fn new(name: impl Into<String>) -> Self {
128 Self {
129 name: name.into(),
130 value: String::new(),
131 validators: Vec::new(),
132 error: None,
133 custom_error_message: None,
134 touched: false,
135 }
136 }
137
138 pub fn value(mut self, value: impl Into<String>) -> Self {
140 self.value = value.into();
141 self
142 }
143
144 pub fn validate(mut self, validator: Validator) -> Self {
146 self.validators.push(validator);
147 self
148 }
149
150 pub fn error_message(mut self, message: impl Into<String>) -> Self {
152 self.custom_error_message = Some(message.into());
153 self
154 }
155
156 pub fn run_validation(&mut self) -> Option<String> {
158 for validator in &self.validators {
159 if let Some(err) = validator.validate(&self.value) {
160 let error = self.custom_error_message.clone().unwrap_or(err);
161 self.error = Some(error.clone());
162 return Some(error);
163 }
164 }
165 self.error = None;
166 None
167 }
168
169 pub fn is_valid(&self) -> bool {
171 self.error.is_none()
172 }
173}
174
175#[derive(Clone)]
177pub struct FormState {
178 inner: Rc<RefCell<FormStateInner>>,
179}
180
181struct FormStateInner {
182 fields: HashMap<String, FormField>,
183 field_order: Vec<String>,
184}
185
186impl Default for FormState {
187 fn default() -> Self {
188 Self::new()
189 }
190}
191
192impl FormState {
193 pub fn new() -> Self {
195 Self {
196 inner: Rc::new(RefCell::new(FormStateInner {
197 fields: HashMap::new(),
198 field_order: Vec::new(),
199 })),
200 }
201 }
202
203 pub fn field(self, field: FormField) -> Self {
205 let mut inner = self.inner.borrow_mut();
206 let name = field.name.clone();
207 inner.fields.insert(name.clone(), field);
208 if !inner.field_order.contains(&name) {
209 inner.field_order.push(name);
210 }
211 drop(inner);
212 self
213 }
214
215 pub fn get_field(&self, name: &str) -> Option<FormField> {
217 let inner = self.inner.borrow();
218 inner.fields.get(name).cloned()
219 }
220
221 pub fn get_value(&self, name: &str) -> String {
223 let inner = self.inner.borrow();
224 inner
225 .fields
226 .get(name)
227 .map(|f| f.value.clone())
228 .unwrap_or_default()
229 }
230
231 pub fn set_value(&self, name: &str, value: String) {
233 let mut inner = self.inner.borrow_mut();
234 if let Some(field) = inner.fields.get_mut(name) {
235 field.value = value;
236 field.touched = true;
237 field.run_validation();
239 }
240 }
241
242 pub fn get_error(&self, name: &str) -> Option<String> {
244 let inner = self.inner.borrow();
245 inner.fields.get(name).and_then(|f| f.error.clone())
246 }
247
248 pub fn is_touched(&self, name: &str) -> bool {
250 let inner = self.inner.borrow();
251 inner.fields.get(name).map(|f| f.touched).unwrap_or(false)
252 }
253
254 pub fn touch(&self, name: &str) {
256 let mut inner = self.inner.borrow_mut();
257 if let Some(field) = inner.fields.get_mut(name) {
258 field.touched = true;
259 field.run_validation();
261 }
262 }
263
264 pub fn validate(&self) -> bool {
266 let mut inner = self.inner.borrow_mut();
267 let mut all_valid = true;
268 for field in inner.fields.values_mut() {
269 if field.run_validation().is_some() {
270 all_valid = false;
271 }
272 }
273 all_valid
274 }
275
276 pub fn is_valid(&self) -> bool {
278 let inner = self.inner.borrow();
279 inner.fields.values().all(|f| f.error.is_none())
280 }
281
282 pub fn values(&self) -> HashMap<String, String> {
284 let inner = self.inner.borrow();
285 inner
286 .fields
287 .iter()
288 .map(|(k, v)| (k.clone(), v.value.clone()))
289 .collect()
290 }
291
292 pub fn field_names(&self) -> Vec<String> {
294 let inner = self.inner.borrow();
295 inner.field_order.clone()
296 }
297
298 pub fn reset(&self) {
300 let mut inner = self.inner.borrow_mut();
301 for field in inner.fields.values_mut() {
302 field.value = String::new();
303 field.error = None;
304 field.touched = false;
305 }
306 }
307
308 pub fn errors(&self) -> HashMap<String, String> {
310 let inner = self.inner.borrow();
311 inner
312 .fields
313 .iter()
314 .filter_map(|(k, v)| v.error.as_ref().map(|e| (k.clone(), e.clone())))
315 .collect()
316 }
317}
318
319pub struct FieldBuilder {
321 field: FormField,
322}
323
324impl FieldBuilder {
325 pub fn new(name: impl Into<String>) -> Self {
327 Self {
328 field: FormField::new(name),
329 }
330 }
331
332 pub fn value(mut self, value: impl Into<String>) -> Self {
334 self.field.value = value.into();
335 self
336 }
337
338 pub fn required(mut self) -> Self {
340 self.field.validators.push(Validator::Required);
341 self
342 }
343
344 pub fn min_length(mut self, min: usize) -> Self {
346 self.field.validators.push(Validator::MinLength(min));
347 self
348 }
349
350 pub fn max_length(mut self, max: usize) -> Self {
352 self.field.validators.push(Validator::MaxLength(max));
353 self
354 }
355
356 pub fn email(mut self) -> Self {
358 self.field.validators.push(Validator::Email);
359 self
360 }
361
362 pub fn number(mut self) -> Self {
364 self.field.validators.push(Validator::Number);
365 self
366 }
367
368 pub fn integer(mut self) -> Self {
370 self.field.validators.push(Validator::Integer);
371 self
372 }
373
374 pub fn pattern(mut self, pattern: &str) -> Result<Self, regex::Error> {
376 self.field
377 .validators
378 .push(Validator::Pattern(Regex::new(pattern)?));
379 Ok(self)
380 }
381
382 pub fn custom<F>(mut self, f: F) -> Self
384 where
385 F: Fn(&str) -> Option<String> + 'static,
386 {
387 self.field.validators.push(Validator::Custom(Rc::new(f)));
388 self
389 }
390
391 pub fn error_message(mut self, message: impl Into<String>) -> Self {
393 self.field.custom_error_message = Some(message.into());
394 self
395 }
396
397 pub fn build(self) -> FormField {
399 self.field
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_required_validator() {
409 assert!(Validator::Required.validate("").is_some());
410 assert!(Validator::Required.validate(" ").is_some());
411 assert!(Validator::Required.validate("hello").is_none());
412 }
413
414 #[test]
415 fn test_min_length_validator() {
416 let validator = Validator::MinLength(5);
417 assert!(validator.validate("hi").is_some());
418 assert!(validator.validate("hello").is_none());
419 assert!(validator.validate("hello world").is_none());
420 }
421
422 #[test]
423 fn test_email_validator() {
424 assert!(Validator::Email.validate("invalid").is_some());
425 assert!(Validator::Email.validate("test@example.com").is_none());
426 assert!(Validator::Email.validate("").is_none()); }
428
429 #[test]
430 fn test_form_state() {
431 let form = FormState::new()
432 .field(FieldBuilder::new("email").required().email().build())
433 .field(
434 FieldBuilder::new("password")
435 .required()
436 .min_length(8)
437 .build(),
438 );
439
440 assert!(!form.validate());
442
443 form.set_value("email", "test@example.com".to_string());
445 form.set_value("password", "password123".to_string());
446 assert!(form.validate());
447 assert!(form.is_valid());
448
449 form.set_value("email", "invalid".to_string());
451 assert!(!form.is_valid());
452 }
453
454 #[test]
455 fn test_custom_validator() {
456 let field = FieldBuilder::new("username")
457 .custom(|v| {
458 if v.contains(' ') {
459 Some("Username cannot contain spaces".to_string())
460 } else {
461 None
462 }
463 })
464 .build();
465
466 let form = FormState::new().field(field);
467
468 form.set_value("username", "hello world".to_string());
469 assert!(!form.is_valid());
470 assert_eq!(
471 form.get_error("username"),
472 Some("Username cannot contain spaces".to_string())
473 );
474
475 form.set_value("username", "helloworld".to_string());
476 assert!(form.is_valid());
477 }
478}