this/core/validation/
validators.rs

1//! Reusable field validators
2//!
3//! These validators are used by the macro system to validate entity fields
4
5use serde_json::Value;
6
7/// Validator: field is required (not null)
8pub fn required() -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
9    |field: &str, value: &Value| {
10        if value.is_null() {
11            Err(format!("Le champ '{}' est requis", field))
12        } else {
13            Ok(())
14        }
15    }
16}
17
18/// Validator: field is optional (always valid)
19pub fn optional() -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
20    |_: &str, _: &Value| Ok(())
21}
22
23/// Validator: number must be positive
24pub fn positive() -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
25    |field: &str, value: &Value| {
26        if let Some(num) = value.as_f64() {
27            if num <= 0.0 {
28                Err(format!(
29                    "Le champ '{}' doit être positif (valeur: {})",
30                    field, num
31                ))
32            } else {
33                Ok(())
34            }
35        } else {
36            Ok(()) // Si ce n'est pas un nombre, on laisse passer (autre validateur gérera)
37        }
38    }
39}
40
41/// Validator: string length must be within range
42pub fn string_length(
43    min: usize,
44    max: usize,
45) -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
46    move |field: &str, value: &Value| {
47        if let Some(s) = value.as_str() {
48            let len = s.len();
49            if len < min {
50                Err(format!(
51                    "'{}' doit avoir au moins {} caractères (actuellement: {})",
52                    field, min, len
53                ))
54            } else if len > max {
55                Err(format!(
56                    "'{}' ne doit pas dépasser {} caractères (actuellement: {})",
57                    field, max, len
58                ))
59            } else {
60                Ok(())
61            }
62        } else {
63            Ok(())
64        }
65    }
66}
67
68/// Validator: number must not exceed maximum
69pub fn max_value(max: f64) -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
70    move |field: &str, value: &Value| {
71        if let Some(num) = value.as_f64() {
72            if num > max {
73                Err(format!(
74                    "'{}' ne doit pas dépasser {} (valeur: {})",
75                    field, max, num
76                ))
77            } else {
78                Ok(())
79            }
80        } else {
81            Ok(())
82        }
83    }
84}
85
86/// Validator: value must be in allowed list
87pub fn in_list(
88    allowed: Vec<String>,
89) -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
90    move |field: &str, value: &Value| {
91        if let Some(s) = value.as_str() {
92            if !allowed.contains(&s.to_string()) {
93                Err(format!(
94                    "'{}' doit être l'une des valeurs: {:?} (valeur actuelle: {})",
95                    field, allowed, s
96                ))
97            } else {
98                Ok(())
99            }
100        } else {
101            Ok(())
102        }
103    }
104}
105
106/// Validator: date must match format
107pub fn date_format(
108    format: &'static str,
109) -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
110    move |field: &str, value: &Value| {
111        if let Some(s) = value.as_str() {
112            match chrono::NaiveDate::parse_from_str(s, format) {
113                Ok(_) => Ok(()),
114                Err(_) => Err(format!(
115                    "'{}' doit être au format {} (valeur actuelle: {})",
116                    field, format, s
117                )),
118            }
119        } else {
120            Ok(())
121        }
122    }
123}