Skip to main content

flyer/validation/
rules.rs

1use crate::request::form::Form;
2use regex::Regex;
3use chrono::{NaiveDate, NaiveDateTime};
4use std::net::IpAddr;
5use std::str::FromStr;
6use reqwest::Url;
7use uuid::Uuid;
8use ulid::Ulid;
9use serde_json::Value as JsonValue;
10
11// TODO: Added new logic if Rule returns empty String error skip all validation
12fn pretty(value: String) -> String {
13    let temp: Vec<&str> = value.split('_').collect();
14    temp.join(" ")
15}
16
17fn get_value(form: &Form, field: &str) -> Option<String> {
18    form.values.get(field).cloned()
19}
20
21pub fn is_empty(form: &Form, field: &str) -> bool {
22    if let Some(val) = form.values.get(field) {
23        return val.is_empty();
24    }
25    if form.files.get(field).is_some() {
26        return false;
27    }
28    true
29}
30
31fn is_present(form: &Form, field: &str) -> bool {
32    form.values.contains_key(field) || form.files.contains_key(field)
33}
34
35// Booleans
36pub async fn accepted<'r>(form: &'r Form, field: String, _args: Vec<String>) -> Option<String> {
37    if let Some(val) = get_value(form, &field) {
38        let accepted_vals = vec!["yes", "on", "1", "true"];
39        if accepted_vals.contains(&val.to_lowercase().as_str()) || val == "1" {
40            return None;
41        }
42    }
43    Some(format!("The {} must be accepted", pretty(field)))
44}
45
46pub async fn accepted_if(form: &Form, field: String, args: Vec<String>) -> Option<String> {
47    if args.len() < 2 { return None; }
48    let another_field = &args[0];
49    let expected_val = &args[1];
50
51    if let Some(val) = get_value(form, another_field) {
52        if val == *expected_val {
53            return accepted(form, field, Vec::new()).await;
54        }
55    }
56    None
57}
58
59pub async fn boolean(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
60    if let Some(val) = get_value(form, &field) {
61        let bool_vals = vec!["true", "false", "1", "0", "on", "off", "yes", "no"];
62        if bool_vals.contains(&val.to_lowercase().as_str()) {
63            return None;
64        }
65    }
66    Some(format!("The {} must be a boolean", pretty(field)))
67}
68
69pub async fn declined(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
70    if let Some(val) = get_value(form, &field) {
71        let declined_vals = vec!["no", "off", "0", "false"];
72        if declined_vals.contains(&val.to_lowercase().as_str()) {
73            return None;
74        }
75    }
76    Some(format!("The {} must be declined", pretty(field)))
77}
78
79pub async fn declined_if(form: &Form, field: String, args: Vec<String>) -> Option<String> {
80    if args.len() < 2 { return None; }
81    let another_field = &args[0];
82    let expected_val = &args[1];
83
84    if let Some(val) = get_value(form, another_field) {
85        if val == *expected_val {
86            return declined(form, field, Vec::new()).await;
87        }
88    }
89    None
90}
91
92// Strings
93pub async fn active_url(_form: &Form, field: String, _args: Vec<String>) -> Option<String> {
94    if let Some(val) = get_value(_form, &field) {
95        if let Ok(url) = Url::parse(&val) {
96            if let Some(host) = url.host_str() {
97                if tokio::net::lookup_host(format!("{}:80", host)).await.is_ok() {
98                    return None;
99                }
100            }
101        }
102    }
103    Some(format!("The {} is not a valid active URL", pretty(field)))
104}
105
106pub async fn alpha(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
107    if let Some(val) = get_value(form, &field) {
108        if !val.is_empty() && val.chars().all(|c| c.is_alphabetic()) {
109            return None;
110        }
111    }
112    Some(format!("The {} must only contain alphabetic characters", pretty(field)))
113}
114
115pub async fn alpha_dash(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
116    if let Some(val) = get_value(form, &field) {
117        if !val.is_empty() && val.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
118            return None;
119        }
120    }
121    Some(format!("The {} must only contain letters, numbers, dashes and underscores", pretty(field)))
122}
123
124pub async fn alpha_numeric(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
125    if let Some(val) = get_value(form, &field) {
126        if !val.is_empty() && val.chars().all(|c| c.is_alphanumeric()) {
127            return None;
128        }
129    }
130    Some(format!("The {} must only contain alphanumeric characters", pretty(field)))
131}
132
133pub async fn ascii(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
134    if let Some(val) = get_value(form, &field) {
135        if val.is_ascii() {
136            return None;
137        }
138    }
139    Some(format!("The {} must only contain ASCII characters", pretty(field)))
140}
141
142pub async fn confirmed(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
143    let confirmation_field = format!("{}_confirmation", field);
144    if let Some(val) = get_value(form, &field) {
145        if let Some(conf) = get_value(form, &confirmation_field) {
146            if val == conf {
147                return None;
148            }
149        }
150    }
151    Some(format!("The {} confirmation does not match", pretty(field)))
152}
153
154pub async fn different(form: &Form, field: String, args: Vec<String>) -> Option<String> {
155    if args.is_empty() { return None; }
156    let another_field = &args[0];
157    if let Some(val) = get_value(form, &field) {
158        if let Some(another_val) = get_value(form, another_field) {
159            if val != another_val {
160                return None;
161            }
162        } else {
163            return None; 
164        }
165    }
166    Some(format!("The {} must be different from {}", pretty(field), pretty(another_field.clone())))
167}
168
169pub async fn doesnt_start_with(form: &Form, field: String, args: Vec<String>) -> Option<String> {
170    if let Some(val) = get_value(form, &field) {
171        for arg in &args {
172            if val.starts_with(arg) {
173                return Some(format!("The {} must not start with {}", pretty(field), arg));
174            }
175        }
176    }
177    None
178}
179
180pub async fn doesnt_end_with(form: &Form, field: String, args: Vec<String>) -> Option<String> {
181    if let Some(val) = get_value(form, &field) {
182        for arg in &args {
183            if val.ends_with(arg) {
184                return Some(format!("The {} must not end with {}", pretty(field), arg));
185            }
186        }
187    }
188    None
189}
190
191pub async fn email(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
192    if let Some(val) = get_value(form, &field) {
193        let email_regex = Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap();
194        if email_regex.is_match(&val) { return None; }
195    }
196    Some(format!("The {} must be a valid email address", pretty(field)))
197}
198
199pub async fn ends_with(form: &Form, field: String, args: Vec<String>) -> Option<String> {
200    if let Some(val) = get_value(form, &field) {
201        for arg in &args {
202            if val.ends_with(arg) {
203                return None;
204            }
205        }
206    }
207    Some(format!("The {} must end with one of: {}", pretty(field), args.join(", ")))
208}
209
210pub async fn hex_color(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
211    if let Some(val) = get_value(form, &field) {
212        let hex_regex = Regex::new(r"^#?([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$").unwrap();
213        if hex_regex.is_match(&val) { return None; }
214    }
215    Some(format!("The {} must be a valid hexadecimal color", pretty(field)))
216}
217
218pub async fn in_rule(form: &Form, field: String, args: Vec<String>) -> Option<String> {
219    if let Some(val) = get_value(form, &field) {
220        if args.contains(&val) {
221            return None;
222        }
223    }
224    Some(format!("The selected {} is invalid", pretty(field)))
225}
226
227pub async fn ip(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
228    if let Some(val) = get_value(form, &field) {
229        if IpAddr::from_str(&val).is_ok() { return None; }
230    }
231    Some(format!("The {} must be a valid IP address", pretty(field)))
232}
233
234pub async fn ipv4(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
235    if let Some(val) = get_value(form, &field) {
236        if let Ok(IpAddr::V4(_)) = IpAddr::from_str(&val) { return None; }
237    }
238    Some(format!("The {} must be a valid IPv4 address", pretty(field)))
239}
240
241pub async fn ipv6(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
242    if let Some(val) = get_value(form, &field) {
243        if let Ok(IpAddr::V6(_)) = IpAddr::from_str(&val) { return None; }
244    }
245    Some(format!("The {} must be a valid IPv6 address", pretty(field)))
246}
247
248pub async fn json(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
249    if let Some(val) = get_value(form, &field) {
250        if serde_json::from_str::<JsonValue>(&val).is_ok() { return None; }
251    }
252    Some(format!("The {} must be a valid JSON string", pretty(field)))
253}
254
255pub async fn lowercase(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
256    if let Some(val) = get_value(form, &field) {
257        if val == val.to_lowercase() { return None; }
258    }
259    Some(format!("The {} must be lowercase", pretty(field)))
260}
261
262pub async fn mac_address(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
263    if let Some(val) = get_value(form, &field) {
264        let mac_regex = Regex::new(r"^([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})$").unwrap();
265        if mac_regex.is_match(&val) { return None; }
266    }
267    Some(format!("The {} must be a valid MAC address", pretty(field)))
268}
269
270pub async fn not_in(form: &Form, field: String, args: Vec<String>) -> Option<String> {
271    if let Some(val) = get_value(form, &field) {
272        if !args.contains(&val) {
273            return None;
274        }
275    }
276    Some(format!("The selected {} is invalid", pretty(field)))
277}
278
279pub async fn regex(form: &Form, field: String, args: Vec<String>) -> Option<String> {
280    if args.is_empty() { return None; }
281    if let Some(val) = get_value(form, &field) {
282        if let Ok(re) = Regex::new(&args[0]) {
283            if re.is_match(&val) { return None; }
284        }
285    }
286    Some(format!("The {} format is invalid", pretty(field)))
287}
288
289pub async fn not_regex(form: &Form, field: String, args: Vec<String>) -> Option<String> {
290    if args.is_empty() { return None; }
291    if let Some(val) = get_value(form, &field) {
292        if let Ok(re) = Regex::new(&args[0]) {
293            if !re.is_match(&val) { return None; }
294        }
295    }
296    Some(format!("The {} format is invalid", pretty(field)))
297}
298
299pub async fn same(form: &Form, field: String, args: Vec<String>) -> Option<String> {
300    if args.is_empty() { return None; }
301    let another_field = &args[0];
302    if let Some(val) = get_value(form, &field) {
303        if let Some(another_val) = get_value(form, another_field) {
304            if val == another_val { return None; }
305        }
306    }
307    Some(format!("The {} and {} must match", pretty(field), pretty(another_field.clone())))
308}
309
310pub async fn starts_with(form: &Form, field: String, args: Vec<String>) -> Option<String> {
311    if let Some(val) = get_value(form, &field) {
312        for arg in &args {
313            if val.starts_with(arg) {
314                return None;
315            }
316        }
317    }
318    Some(format!("The {} must start with one of: {}", pretty(field), args.join(", ")))
319}
320
321pub async fn uppercase(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
322    if let Some(val) = get_value(form, &field) {
323        if val == val.to_uppercase() { return None; }
324    }
325    Some(format!("The {} must be uppercase", pretty(field)))
326}
327
328pub async fn url(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
329    if let Some(val) = get_value(form, &field) {
330        if Url::parse(&val).is_ok() { return None; }
331    }
332    Some(format!("The {} must be a valid URL", pretty(field)))
333}
334
335pub async fn uuid(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
336    if let Some(val) = get_value(form, &field) {
337        if Uuid::parse_str(&val).is_ok() { return None; }
338    }
339    Some(format!("The {} must be a valid UUID", pretty(field)))
340}
341
342pub async fn ulid(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
343    if let Some(val) = get_value(form, &field) {
344        if Ulid::from_str(&val).is_ok() { return None; }
345    }
346    Some(format!("The {} must be a valid ULID", pretty(field)))
347}
348
349// Numbers
350pub async fn between(form: &Form, field: String, args: Vec<String>) -> Option<String> {
351    if args.len() < 2 { return None; }
352    let min: f64 = args[0].parse().unwrap_or(0.0);
353    let max: f64 = args[1].parse().unwrap_or(0.0);
354
355    if let Some(val) = get_value(form, &field) {
356        if let Ok(num) = val.parse::<f64>() {
357            if num >= min && num <= max { return None; }
358            return Some(format!("The {} must be between {} and {}", pretty(field), min, max));
359        }
360        if val.len() >= min as usize && val.len() <= max as usize { return None; }
361        return Some(format!("The {} must be between {} and {} characters", pretty(field), min, max));
362    }
363    if let Some(file) = form.files.get(&field) {
364        let size_kb = file.content.len() / 1024;
365        if size_kb >= min as usize && size_kb <= max as usize { return None; }
366        return Some(format!("The {} must be between {} and {} kilobytes", pretty(field), min, max));
367    }
368    None
369}
370
371pub async fn decimal(form: &Form, field: String, args: Vec<String>) -> Option<String> {
372    if let Some(val) = get_value(form, &field) {
373        if let Ok(_) = val.parse::<f64>() {
374            if val.contains('.') {
375                let parts: Vec<&str> = val.split('.').collect();
376                if parts.len() == 2 {
377                    let decimal_places = parts[1].len();
378                    if args.len() >= 1 {
379                        let min: usize = args[0].parse().unwrap_or(0);
380                        if args.len() >= 2 {
381                             let max: usize = args[1].parse().unwrap_or(min);
382                             if decimal_places >= min && decimal_places <= max { return None; }
383                             return Some(format!("The {} must have between {} and {} decimal places", pretty(field), min, max));
384                        }
385                        if decimal_places == min { return None; }
386                        return Some(format!("The {} must have {} decimal places", pretty(field), min));
387                    }
388                    return None;
389                }
390            }
391        }
392    }
393    Some(format!("The {} must be a decimal", pretty(field)))
394}
395
396pub async fn digits(form: &Form, field: String, args: Vec<String>) -> Option<String> {
397    if args.is_empty() { return None; }
398    let len: usize = args[0].parse().unwrap_or(0);
399    if let Some(val) = get_value(form, &field) {
400        if val.chars().all(|c| c.is_numeric()) && val.len() == len {
401            return None;
402        }
403    }
404    Some(format!("The {} must be {} digits", pretty(field), len))
405}
406
407pub async fn digits_between(form: &Form, field: String, args: Vec<String>) -> Option<String> {
408    if args.len() < 2 { return None; }
409    let min: usize = args[0].parse().unwrap_or(0);
410    let max: usize = args[1].parse().unwrap_or(0);
411    if let Some(val) = get_value(form, &field) {
412        if val.chars().all(|c| c.is_numeric()) && val.len() >= min && val.len() <= max {
413            return None;
414        }
415    }
416    Some(format!("The {} must be between {} and {} digits", pretty(field), min, max))
417}
418
419pub async fn gt(form: &Form, field: String, args: Vec<String>) -> Option<String> {
420    if args.is_empty() { return None; }
421    let another_field = &args[0];
422    if let Some(val) = get_value(form, &field) {
423        if let Ok(v1) = val.parse::<f64>() {
424            if let Some(another_val) = get_value(form, another_field) {
425                if let Ok(v2) = another_val.parse::<f64>() {
426                    if v1 > v2 { return None; }
427                }
428            } else if let Ok(v2) = another_field.parse::<f64>() {
429                 if v1 > v2 { return None; }
430            }
431        }
432    }
433    Some(format!("The {} must be greater than {}", pretty(field), pretty(another_field.clone())))
434}
435
436pub async fn gte(form: &Form, field: String, args: Vec<String>) -> Option<String> {
437    if args.is_empty() { return None; }
438    let another_field = &args[0];
439    if let Some(val) = get_value(form, &field) {
440        if let Ok(v1) = val.parse::<f64>() {
441            if let Some(another_val) = get_value(form, another_field) {
442                if let Ok(v2) = another_val.parse::<f64>() {
443                    if v1 >= v2 { return None; }
444                }
445            } else if let Ok(v2) = another_field.parse::<f64>() {
446                 if v1 >= v2 { return None; }
447            }
448        }
449    }
450    Some(format!("The {} must be greater than or equal {}", pretty(field), pretty(another_field.clone())))
451}
452
453pub async fn integer(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
454    if let Some(val) = get_value(form, &field) {
455        if val.parse::<i128>().is_ok() { return None; }
456    }
457    Some(format!("The {} must be an integer", pretty(field)))
458}
459
460pub async fn lt(form: &Form, field: String, args: Vec<String>) -> Option<String> {
461    if args.is_empty() { return None; }
462    let another_field = &args[0];
463    if let Some(val) = get_value(form, &field) {
464        if let Ok(v1) = val.parse::<f64>() {
465            if let Some(another_val) = get_value(form, another_field) {
466                if let Ok(v2) = another_val.parse::<f64>() {
467                    if v1 < v2 { return None; }
468                }
469            } else if let Ok(v2) = another_field.parse::<f64>() {
470                 if v1 < v2 { return None; }
471            }
472        }
473    }
474    Some(format!("The {} must be less than {}", pretty(field), pretty(another_field.clone())))
475}
476
477pub async fn lte(form: &Form, field: String, args: Vec<String>) -> Option<String> {
478    if args.is_empty() { return None; }
479    let another_field = &args[0];
480    if let Some(val) = get_value(form, &field) {
481        if let Ok(v1) = val.parse::<f64>() {
482            if let Some(another_val) = get_value(form, another_field) {
483                if let Ok(v2) = another_val.parse::<f64>() {
484                    if v1 <= v2 { return None; }
485                }
486            } else if let Ok(v2) = another_field.parse::<f64>() {
487                 if v1 <= v2 { return None; }
488            }
489        }
490    }
491    Some(format!("The {} must be less than or equal {}", pretty(field), pretty(another_field.clone())))
492}
493
494pub async fn max(form: &Form, field: String, args: Vec<String>) -> Option<String> {
495    if args.is_empty() { return None; }
496    let max_val: f64 = args[0].parse().unwrap_or(0.0);
497
498    if let Some(val) = get_value(form, &field) {
499        if let Ok(num) = val.parse::<f64>() {
500            if num <= max_val { return None; }
501            return Some(format!("The {} must not be greater than {}", pretty(field), max_val));
502        }
503        if val.len() <= max_val as usize { return None; }
504        return Some(format!("The {} must not be greater than {} characters", pretty(field), max_val));
505    }
506    if let Some(file) = form.files.get(&field) {
507        let size_kb = file.content.len() / 1024;
508        if size_kb <= max_val as usize { return None; }
509        return Some(format!("The {} must not be greater than {} kilobytes", pretty(field), max_val));
510    }
511    None
512}
513
514pub async fn min(form: &Form, field: String, args: Vec<String>) -> Option<String> {
515    if args.is_empty() { return None; }
516    let min_val: f64 = args[0].parse().unwrap_or(0.0);
517
518    if let Some(val) = get_value(form, &field) {
519        if let Ok(num) = val.parse::<f64>() {
520            if num >= min_val { return None; }
521            return Some(format!("The {} must be at least {}", pretty(field), min_val));
522        }
523        if val.len() >= min_val as usize { return None; }
524        return Some(format!("The {} must be at least {} characters", pretty(field), min_val));
525    }
526    if let Some(file) = form.files.get(&field) {
527        let size_kb = file.content.len() / 1024;
528        if size_kb >= min_val as usize { return None; }
529        return Some(format!("The {} must be at least {} kilobytes", pretty(field), min_val));
530    }
531    None
532}
533
534pub async fn max_digits(form: &Form, field: String, args: Vec<String>) -> Option<String> {
535    if args.is_empty() { return None; }
536    let max: usize = args[0].parse().unwrap_or(0);
537    if let Some(val) = get_value(form, &field) {
538        if val.chars().all(|c| c.is_numeric()) && val.len() <= max {
539            return None;
540        }
541    }
542    Some(format!("The {} must not have more than {} digits", pretty(field), max))
543}
544
545pub async fn min_digits(form: &Form, field: String, args: Vec<String>) -> Option<String> {
546    if args.is_empty() { return None; }
547    let min: usize = args[0].parse().unwrap_or(0);
548    if let Some(val) = get_value(form, &field) {
549        if val.chars().all(|c| c.is_numeric()) && val.len() >= min {
550            return None;
551        }
552    }
553    Some(format!("The {} must have at least {} digits", pretty(field), min))
554}
555
556pub async fn multiple_of(form: &Form, field: String, args: Vec<String>) -> Option<String> {
557    if args.is_empty() { return None; }
558    let factor: f64 = args[0].parse().unwrap_or(1.0);
559    if let Some(val) = get_value(form, &field) {
560        if let Ok(num) = val.parse::<f64>() {
561            if num % factor == 0.0 { return None; }
562        }
563    }
564    Some(format!("The {} must be a multiple of {}", pretty(field), factor))
565}
566
567pub async fn numeric(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
568    if let Some(val) = get_value(form, &field) {
569        if val.parse::<f64>().is_ok() { return None; }
570    }
571    Some(format!("The {} must be a number", pretty(field)))
572}
573
574// Dates
575fn parse_date(date_str: &str) -> Option<NaiveDateTime> {
576    let formats = vec![
577        "%Y-%m-%d %H:%M:%S",
578        "%Y-%m-%d",
579        "%d-%m-%Y",
580        "%m/%d/%Y",
581    ];
582    for format in formats {
583        if let Ok(dt) = NaiveDateTime::parse_from_str(date_str, format) {
584            return Some(dt);
585        }
586        if let Ok(d) = NaiveDate::parse_from_str(date_str, format) {
587            return Some(d.and_hms_opt(0, 0, 0).unwrap());
588        }
589    }
590    None
591}
592
593pub async fn after(form: &Form, field: String, args: Vec<String>) -> Option<String> {
594    if args.is_empty() { return None; }
595    let target_date_str = if let Some(val) = get_value(form, &args[0]) { val } else { args[0].clone() };
596    if let Some(val) = get_value(form, &field) {
597        if let (Some(d1), Some(d2)) = (parse_date(&val), parse_date(&target_date_str)) {
598            if d1 > d2 { return None; }
599        }
600    }
601    Some(format!("The {} must be a date after {}", pretty(field), target_date_str))
602}
603
604pub async fn after_or_equal(form: &Form, field: String, args: Vec<String>) -> Option<String> {
605    if args.is_empty() { return None; }
606    let target_date_str = if let Some(val) = get_value(form, &args[0]) { val } else { args[0].clone() };
607    if let Some(val) = get_value(form, &field) {
608        if let (Some(d1), Some(d2)) = (parse_date(&val), parse_date(&target_date_str)) {
609            if d1 >= d2 { return None; }
610        }
611    }
612    Some(format!("The {} must be a date after or equal to {}", pretty(field), target_date_str))
613}
614
615pub async fn before(form: &Form, field: String, args: Vec<String>) -> Option<String> {
616    if args.is_empty() { return None; }
617    let target_date_str = if let Some(val) = get_value(form, &args[0]) { val } else { args[0].clone() };
618    if let Some(val) = get_value(form, &field) {
619        if let (Some(d1), Some(d2)) = (parse_date(&val), parse_date(&target_date_str)) {
620            if d1 < d2 { return None; }
621        }
622    }
623    Some(format!("The {} must be a date before {}", pretty(field), target_date_str))
624}
625
626pub async fn before_or_equal(form: &Form, field: String, args: Vec<String>) -> Option<String> {
627    if args.is_empty() { return None; }
628    let target_date_str = if let Some(val) = get_value(form, &args[0]) { val } else { args[0].clone() };
629    if let Some(val) = get_value(form, &field) {
630        if let (Some(d1), Some(d2)) = (parse_date(&val), parse_date(&target_date_str)) {
631            if d1 <= d2 { return None; }
632        }
633    }
634    Some(format!("The {} must be a date before or equal to {}", pretty(field), target_date_str))
635}
636
637pub async fn date(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
638    if let Some(val) = get_value(form, &field) {
639        if parse_date(&val).is_some() { return None; }
640    }
641    Some(format!("The {} is not a valid date", pretty(field)))
642}
643
644pub async fn date_equals(form: &Form, field: String, args: Vec<String>) -> Option<String> {
645    if args.is_empty() { return None; }
646    let target_date_str = if let Some(val) = get_value(form, &args[0]) { val } else { args[0].clone() };
647    if let Some(val) = get_value(form, &field) {
648        if let (Some(d1), Some(d2)) = (parse_date(&val), parse_date(&target_date_str)) {
649            if d1 == d2 { return None; }
650        }
651    }
652    Some(format!("The {} must be a date equal to {}", pretty(field), target_date_str))
653}
654
655pub async fn date_format(form: &Form, field: String, args: Vec<String>) -> Option<String> {
656    if args.is_empty() { return None; }
657    let format = &args[0];
658    if let Some(val) = get_value(form, &field) {
659        if NaiveDateTime::parse_from_str(&val, format).is_ok() || NaiveDate::parse_from_str(&val, format).is_ok() {
660            return None;
661        }
662    }
663    Some(format!("The {} does not match the format {}", pretty(field), format))
664}
665
666// Files
667pub async fn file(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
668    if form.files.get(&field).is_some() { return None; }
669    Some(format!("The {} must be a file", pretty(field)))
670}
671
672pub async fn image(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
673    if let Some(file) = form.files.get(&field) {
674        let image_mimes = vec!["image/jpeg", "image/png", "image/gif", "image/bmp", "image/svg+xml", "image/webp"];
675        if image_mimes.contains(&file.mime.as_str()) {
676            return None;
677        }
678    }
679    Some(format!("The {} must be an image", pretty(field)))
680}
681
682pub async fn mimetypes(form: &Form, field: String, args: Vec<String>) -> Option<String> {
683    if let Some(file) = form.files.get(&field) {
684        if args.contains(&file.mime) {
685            return None;
686        }
687    }
688    Some(format!("The {} must be a file of type: {}", pretty(field), args.join(", ")))
689}
690
691pub async fn mimes(form: &Form, field: String, args: Vec<String>) -> Option<String> {
692    if let Some(file) = form.files.get(&field) {
693        let parts: Vec<&str> = file.name.split('.').collect();
694        if let Some(ext) = parts.last() {
695            if args.contains(&ext.to_string()) {
696                return None;
697            }
698        }
699    }
700    Some(format!("The {} must be a file of type: {}", pretty(field), args.join(", ")))
701}
702
703pub async fn extensions(form: &Form, field: String, args: Vec<String>) -> Option<String> {
704    mimes(form, field, args).await
705}
706
707// Utilities
708pub async fn required(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
709    if !is_empty(form, &field) { return None; }
710    Some(format!("The {} field is required", pretty(field)))
711}
712
713pub async fn required_if(form: &Form, field: String, args: Vec<String>) -> Option<String> {
714    if args.len() < 2 { return None; }
715    let another_field = &args[0];
716    let expected_val = &args[1];
717
718    if let Some(val) = get_value(form, another_field) {
719        if val == *expected_val {
720            return required(form, field, Vec::new()).await;
721        }
722    }
723    None
724}
725
726pub async fn required_if_accepted(form: &Form, field: String, args: Vec<String>) -> Option<String> {
727    if args.is_empty() { return None; }
728    let another_field = &args[0];
729    if accepted(form, another_field.clone(), Vec::new()).await.is_none() {
730        return required(form, field, Vec::new()).await;
731    }
732    None
733}
734
735pub async fn required_unless(form: &Form, field: String, args: Vec<String>) -> Option<String> {
736    if args.len() < 2 { return None; }
737    let another_field = &args[0];
738    let expected_val = &args[1];
739
740    if let Some(val) = get_value(form, another_field) {
741        if val != *expected_val {
742            return required(form, field, Vec::new()).await;
743        }
744    } else {
745        return required(form, field, Vec::new()).await;
746    }
747    None
748}
749
750pub async fn required_with(form: &Form, field: String, args: Vec<String>) -> Option<String> {
751    for arg in &args {
752        if !is_empty(form, arg) {
753            return required(form, field, Vec::new()).await;
754        }
755    }
756    None
757}
758
759pub async fn required_without(form: &Form, field: String, args: Vec<String>) -> Option<String> {
760    if let Some(err) = required(form, field.clone(), args.clone()).await {
761        for arg in args {
762            if let Some(_) = required(form, String::from(arg.to_string()), Default::default()).await {
763                return Some(err);
764            }
765        }
766    }
767    Some(String::new())
768}
769
770pub async fn required_with_all(form: &Form, field: String, args: Vec<String>) -> Option<String> {
771    for arg in &args {
772        if is_empty(form, arg) {
773            return None;
774        }
775    }
776    required(form, field, Vec::new()).await
777}
778
779pub async fn required_without_all(form: &Form, field: String, args: Vec<String>) -> Option<String> {
780    for arg in &args {
781        if !is_empty(form, arg) {
782            return None;
783        }
784    }
785    required(form, field, Vec::new()).await
786}
787
788pub async fn prohibited(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
789    if is_empty(form, &field) { return None; }
790    Some(format!("The {} field is prohibited", pretty(field)))
791}
792
793pub async fn prohibited_if(form: &Form, field: String, args: Vec<String>) -> Option<String> {
794    if args.len() < 2 { return None; }
795    let another_field = &args[0];
796    let expected_val = &args[1];
797
798    if let Some(val) = get_value(form, another_field) {
799        if val == *expected_val {
800            return prohibited(form, field, Vec::new()).await;
801        }
802    }
803    None
804}
805
806pub async fn prohibited_unless(form: &Form, field: String, args: Vec<String>) -> Option<String> {
807    if args.len() < 2 { return None; }
808    let another_field = &args[0];
809    let expected_val = &args[1];
810
811    if let Some(val) = get_value(form, another_field) {
812        if val != *expected_val {
813            return prohibited(form, field, Vec::new()).await;
814        }
815    } else {
816        return prohibited(form, field, Vec::new()).await;
817    }
818    None
819}
820
821pub async fn prohibited_with(form: &Form, field: String, args: Vec<String>) -> Option<String> {
822    for arg in &args {
823        if !is_empty(form, arg) {
824            return prohibited(form, field, Vec::new()).await;
825        }
826    }
827    None
828}
829
830pub async fn prohibited_with_all(form: &Form, field: String, args: Vec<String>) -> Option<String> {
831    for arg in &args {
832        if is_empty(form, arg) {
833            return None;
834        }
835    }
836    prohibited(form, field, Vec::new()).await
837}
838
839pub async fn filled(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
840    if is_present(form, &field) && is_empty(form, &field) {
841        return Some(format!("The {} field must have a value", pretty(field)));
842    }
843    None
844}
845
846pub async fn missing(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
847    if is_present(form, &field) {
848        return Some(format!("The {} field must be missing", pretty(field)));
849    }
850    None
851}
852
853pub async fn missing_if(form: &Form, field: String, args: Vec<String>) -> Option<String> {
854    if args.len() < 2 { return None; }
855    let another_field = &args[0];
856    let expected_val = &args[1];
857    if let Some(val) = get_value(form, another_field) {
858        if val == *expected_val {
859            return missing(form, field, Vec::new()).await;
860        }
861    }
862    None
863}
864
865pub async fn missing_unless(form: &Form, field: String, args: Vec<String>) -> Option<String> {
866    if args.len() < 2 { return None; }
867    let another_field = &args[0];
868    let expected_val = &args[1];
869    if let Some(val) = get_value(form, another_field) {
870        if val != *expected_val {
871            return missing(form, field, Vec::new()).await;
872        }
873    } else {
874        return missing(form, field, Vec::new()).await;
875    }
876    None
877}
878
879pub async fn present(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
880    if !is_present(form, &field) {
881        return Some(format!("The {} field must be present", pretty(field)));
882    }
883    None
884}
885
886pub async fn present_if(form: &Form, field: String, args: Vec<String>) -> Option<String> {
887    if args.len() < 2 { return None; }
888    let another_field = &args[0];
889    let expected_val = &args[1];
890    if let Some(val) = get_value(form, another_field) {
891        if val == *expected_val {
892            return present(form, field, Vec::new()).await;
893        }
894    }
895    None
896}
897
898pub async fn present_unless(form: &Form, field: String, args: Vec<String>) -> Option<String> {
899    if args.len() < 2 { return None; }
900    let another_field = &args[0];
901    let expected_val = &args[1];
902    if let Some(val) = get_value(form, another_field) {
903        if val != *expected_val {
904            return present(form, field, Vec::new()).await;
905        }
906    } else {
907        return present(form, field, Vec::new()).await;
908    }
909    None
910}
911
912pub async fn string(form: &Form, field: String, _args: Vec<String>) -> Option<String> {
913    if get_value(form, &field).is_some() { return None; }
914    Some(format!("The {} must be a string", pretty(field)))
915}
916
917pub async fn size(form: &Form, field: String, args: Vec<String>) -> Option<String> {
918    if args.is_empty() { return None; }
919    let size: usize = args[0].parse().unwrap_or(0);
920
921    if let Some(val) = get_value(form, &field) {
922        if let Ok(num) = val.parse::<f64>() {
923            if num == size as f64 { return None; }
924            return Some(format!("The {} must be {}", pretty(field), size));
925        }
926        if val.len() == size { return None; }
927        return Some(format!("The {} must be {} characters", pretty(field), size));
928    }
929    if let Some(file) = form.files.get(&field) {
930        let size_kb = file.content.len() / 1024;
931        if size_kb == size { return None; }
932        return Some(format!("The {} must be {} kilobytes", pretty(field), size));
933    }
934    None
935}
936