1use serde_json::Value;
6
7pub 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
18pub fn optional() -> impl Fn(&str, &Value) -> Result<(), String> + Send + Sync + Clone {
20 |_: &str, _: &Value| Ok(())
21}
22
23pub 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(()) }
38 }
39}
40
41pub 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
68pub 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
86pub 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
106pub 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}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use serde_json::json;
129
130 #[test]
133 fn test_required_null_value_returns_error() {
134 let v = required();
135 let result = v("name", &json!(null));
136 assert!(result.is_err());
137 assert!(result.unwrap_err().contains("requis"));
138 }
139
140 #[test]
141 fn test_required_string_value_returns_ok() {
142 let v = required();
143 assert!(v("name", &json!("hello")).is_ok());
144 }
145
146 #[test]
147 fn test_required_number_value_returns_ok() {
148 let v = required();
149 assert!(v("age", &json!(42)).is_ok());
150 }
151
152 #[test]
153 fn test_required_bool_value_returns_ok() {
154 let v = required();
155 assert!(v("active", &json!(true)).is_ok());
156 }
157
158 #[test]
159 fn test_required_object_value_returns_ok() {
160 let v = required();
161 assert!(v("data", &json!({"key": "val"})).is_ok());
162 }
163
164 #[test]
165 fn test_required_empty_string_returns_ok() {
166 let v = required();
167 assert!(v("name", &json!("")).is_ok());
168 }
169
170 #[test]
171 fn test_required_array_returns_ok() {
172 let v = required();
173 assert!(v("tags", &json!([1, 2, 3])).is_ok());
174 }
175
176 #[test]
179 fn test_optional_always_ok_for_null() {
180 let v = optional();
181 assert!(v("field", &json!(null)).is_ok());
182 }
183
184 #[test]
185 fn test_optional_always_ok_for_string() {
186 let v = optional();
187 assert!(v("field", &json!("value")).is_ok());
188 }
189
190 #[test]
193 fn test_positive_negative_number_returns_error() {
194 let v = positive();
195 let result = v("price", &json!(-5.0));
196 assert!(result.is_err());
197 assert!(result.unwrap_err().contains("positif"));
198 }
199
200 #[test]
201 fn test_positive_zero_returns_error() {
202 let v = positive();
203 assert!(v("price", &json!(0.0)).is_err());
204 }
205
206 #[test]
207 fn test_positive_positive_number_returns_ok() {
208 let v = positive();
209 assert!(v("price", &json!(42.5)).is_ok());
210 }
211
212 #[test]
213 fn test_positive_non_number_passthrough() {
214 let v = positive();
215 assert!(v("name", &json!("hello")).is_ok());
216 }
217
218 #[test]
219 fn test_positive_integer_positive() {
220 let v = positive();
221 assert!(v("count", &json!(1)).is_ok());
222 }
223
224 #[test]
225 fn test_positive_integer_negative() {
226 let v = positive();
227 assert!(v("count", &json!(-1)).is_err());
228 }
229
230 #[test]
233 fn test_string_length_too_short_returns_error() {
234 let v = string_length(3, 50);
235 let result = v("name", &json!("ab"));
236 assert!(result.is_err());
237 assert!(result.unwrap_err().contains("au moins 3"));
238 }
239
240 #[test]
241 fn test_string_length_too_long_returns_error() {
242 let v = string_length(1, 5);
243 let result = v("name", &json!("abcdef"));
244 assert!(result.is_err());
245 assert!(result.unwrap_err().contains("dépasser 5"));
246 }
247
248 #[test]
249 fn test_string_length_exact_min_returns_ok() {
250 let v = string_length(3, 10);
251 assert!(v("name", &json!("abc")).is_ok());
252 }
253
254 #[test]
255 fn test_string_length_exact_max_returns_ok() {
256 let v = string_length(1, 5);
257 assert!(v("name", &json!("abcde")).is_ok());
258 }
259
260 #[test]
261 fn test_string_length_within_range_returns_ok() {
262 let v = string_length(2, 10);
263 assert!(v("name", &json!("hello")).is_ok());
264 }
265
266 #[test]
267 fn test_string_length_non_string_passthrough() {
268 let v = string_length(5, 10);
269 assert!(v("age", &json!(42)).is_ok());
270 }
271
272 #[test]
275 fn test_max_value_over_returns_error() {
276 let v = max_value(100.0);
277 let result = v("score", &json!(101.0));
278 assert!(result.is_err());
279 assert!(result.unwrap_err().contains("dépasser 100"));
280 }
281
282 #[test]
283 fn test_max_value_equal_returns_ok() {
284 let v = max_value(100.0);
285 assert!(v("score", &json!(100.0)).is_ok());
286 }
287
288 #[test]
289 fn test_max_value_under_returns_ok() {
290 let v = max_value(100.0);
291 assert!(v("score", &json!(50.0)).is_ok());
292 }
293
294 #[test]
295 fn test_max_value_non_number_passthrough() {
296 let v = max_value(100.0);
297 assert!(v("name", &json!("hello")).is_ok());
298 }
299
300 #[test]
301 fn test_max_value_negative() {
302 let v = max_value(0.0);
303 assert!(v("temp", &json!(-10.0)).is_ok());
304 }
305
306 #[test]
309 fn test_in_list_value_in_list_returns_ok() {
310 let v = in_list(vec!["active".into(), "inactive".into(), "pending".into()]);
311 assert!(v("status", &json!("active")).is_ok());
312 }
313
314 #[test]
315 fn test_in_list_value_not_in_list_returns_error() {
316 let v = in_list(vec!["active".into(), "inactive".into()]);
317 let result = v("status", &json!("deleted"));
318 assert!(result.is_err());
319 assert!(result.unwrap_err().contains("valeurs"));
320 }
321
322 #[test]
323 fn test_in_list_non_string_passthrough() {
324 let v = in_list(vec!["yes".into(), "no".into()]);
325 assert!(v("flag", &json!(42)).is_ok());
326 }
327
328 #[test]
329 fn test_in_list_empty_list_always_error_for_strings() {
330 let v = in_list(vec![]);
331 assert!(v("status", &json!("anything")).is_err());
332 }
333
334 #[test]
337 fn test_date_format_valid_date_returns_ok() {
338 let v = date_format("%Y-%m-%d");
339 assert!(v("birthday", &json!("2024-01-15")).is_ok());
340 }
341
342 #[test]
343 fn test_date_format_invalid_date_returns_error() {
344 let v = date_format("%Y-%m-%d");
345 let result = v("birthday", &json!("not-a-date"));
346 assert!(result.is_err());
347 assert!(result.unwrap_err().contains("format"));
348 }
349
350 #[test]
351 fn test_date_format_non_string_passthrough() {
352 let v = date_format("%Y-%m-%d");
353 assert!(v("birthday", &json!(12345)).is_ok());
354 }
355
356 #[test]
357 fn test_date_format_wrong_format_returns_error() {
358 let v = date_format("%d/%m/%Y");
359 assert!(v("date", &json!("2024-01-15")).is_err());
360 }
361
362 #[test]
363 fn test_date_format_correct_custom_format() {
364 let v = date_format("%d/%m/%Y");
365 assert!(v("date", &json!("15/01/2024")).is_ok());
366 }
367}