Skip to main content

leptos_forms_rs/validation/
array_validators.rs

1//! Array validators module - Validation for field arrays
2//!
3//! This module provides validation functions specifically for field arrays including
4//! length validation, item validation, and array-specific validation rules.
5
6use crate::core::types::FieldValue;
7
8/// Array validators implementation
9pub struct ArrayValidators;
10
11impl ArrayValidators {
12    /// Validate minimum array length
13    pub fn min_length(value: &FieldValue, min: usize) -> Result<(), String> {
14        if let FieldValue::Array(arr) = value {
15            if arr.len() >= min {
16                Ok(())
17            } else {
18                Err(format!("Array must have at least {} items", min))
19            }
20        } else {
21            Err("Value must be an array".to_string())
22        }
23    }
24    
25    /// Validate maximum array length
26    pub fn max_length(value: &FieldValue, max: usize) -> Result<(), String> {
27        if let FieldValue::Array(arr) = value {
28            if arr.len() <= max {
29                Ok(())
30            } else {
31                Err(format!("Array must have at most {} items", max))
32            }
33        } else {
34            Err("Value must be an array".to_string())
35        }
36    }
37    
38    /// Validate exact array length
39    pub fn exact_length(value: &FieldValue, exact: usize) -> Result<(), String> {
40        if let FieldValue::Array(arr) = value {
41            if arr.len() == exact {
42                Ok(())
43            } else {
44                Err(format!("Array must have exactly {} items", exact))
45            }
46        } else {
47            Err("Value must be an array".to_string())
48        }
49    }
50    
51    /// Validate that array is not empty
52    pub fn not_empty(value: &FieldValue) -> Result<(), String> {
53        if let FieldValue::Array(arr) = value {
54            if !arr.is_empty() {
55                Ok(())
56            } else {
57                Err("Array cannot be empty".to_string())
58            }
59        } else {
60            Err("Value must be an array".to_string())
61        }
62    }
63    
64    /// Validate that array is empty
65    pub fn empty(value: &FieldValue) -> Result<(), String> {
66        if let FieldValue::Array(arr) = value {
67            if arr.is_empty() {
68                Ok(())
69            } else {
70                Err("Array must be empty".to_string())
71            }
72        } else {
73            Err("Value must be an array".to_string())
74        }
75    }
76    
77    /// Validate array length range
78    pub fn length_range(value: &FieldValue, min: usize, max: usize) -> Result<(), String> {
79        if let FieldValue::Array(arr) = value {
80            if arr.len() >= min && arr.len() <= max {
81                Ok(())
82            } else {
83                Err(format!("Array must have between {} and {} items", min, max))
84            }
85        } else {
86            Err("Value must be an array".to_string())
87        }
88    }
89    
90    /// Validate that all array items are not empty
91    pub fn all_items_not_empty(value: &FieldValue) -> Result<(), String> {
92        if let FieldValue::Array(arr) = value {
93            for (index, item) in arr.iter().enumerate() {
94                if item.is_empty() {
95                    return Err(format!("Item at index {} cannot be empty", index));
96                }
97            }
98            Ok(())
99        } else {
100            Err("Value must be an array".to_string())
101        }
102    }
103    
104    /// Validate that all array items are unique
105    pub fn all_items_unique(value: &FieldValue) -> Result<(), String> {
106        if let FieldValue::Array(arr) = value {
107            for (i, item1) in arr.iter().enumerate() {
108                for (j, item2) in arr.iter().enumerate() {
109                    if i != j && item1 == item2 {
110                        return Err(format!("Duplicate items found at indices {} and {}", i, j));
111                    }
112                }
113            }
114            Ok(())
115        } else {
116            Err("Value must be an array".to_string())
117        }
118    }
119    
120    /// Validate that all array items are strings
121    pub fn all_items_strings(value: &FieldValue) -> Result<(), String> {
122        if let FieldValue::Array(arr) = value {
123            for (index, item) in arr.iter().enumerate() {
124                if !matches!(item, FieldValue::String(_)) {
125                    return Err(format!("Item at index {} must be a string", index));
126                }
127            }
128            Ok(())
129        } else {
130            Err("Value must be an array".to_string())
131        }
132    }
133    
134    /// Validate that all array items are numbers
135    pub fn all_items_numbers(value: &FieldValue) -> Result<(), String> {
136        if let FieldValue::Array(arr) = value {
137            for (index, item) in arr.iter().enumerate() {
138                if !matches!(item, FieldValue::Number(_)) {
139                    return Err(format!("Item at index {} must be a number", index));
140                }
141            }
142            Ok(())
143        } else {
144            Err("Value must be an array".to_string())
145        }
146    }
147    
148    /// Validate that all array items are booleans
149    pub fn all_items_booleans(value: &FieldValue) -> Result<(), String> {
150        if let FieldValue::Array(arr) = value {
151            for (index, item) in arr.iter().enumerate() {
152                if !matches!(item, FieldValue::Boolean(_)) {
153                    return Err(format!("Item at index {} must be a boolean", index));
154                }
155            }
156            Ok(())
157        } else {
158            Err("Value must be an array".to_string())
159        }
160    }
161    
162    /// Validate array items against a custom validator function
163    pub fn validate_items_with<F>(value: &FieldValue, validator: F) -> Result<(), String>
164    where
165        F: Fn(&FieldValue) -> Result<(), String>,
166    {
167        if let FieldValue::Array(arr) = value {
168            for (index, item) in arr.iter().enumerate() {
169                if let Err(error) = validator(item) {
170                    return Err(format!("Item at index {}: {}", index, error));
171                }
172            }
173            Ok(())
174        } else {
175            Err("Value must be an array".to_string())
176        }
177    }
178    
179    /// Validate that array contains at least one item matching a condition
180    pub fn contains<F>(value: &FieldValue, predicate: F) -> Result<(), String>
181    where
182        F: Fn(&FieldValue) -> bool,
183    {
184        if let FieldValue::Array(arr) = value {
185            if arr.iter().any(&predicate) {
186                Ok(())
187            } else {
188                Err("Array must contain at least one item matching the condition".to_string())
189            }
190        } else {
191            Err("Value must be an array".to_string())
192        }
193    }
194    
195    /// Validate that array contains no items matching a condition
196    pub fn contains_none<F>(value: &FieldValue, predicate: F) -> Result<(), String>
197    where
198        F: Fn(&FieldValue) -> bool,
199    {
200        if let FieldValue::Array(arr) = value {
201            if arr.iter().any(&predicate) {
202                Err("Array must not contain any items matching the condition".to_string())
203            } else {
204                Ok(())
205            }
206        } else {
207            Err("Value must be an array".to_string())
208        }
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215    
216    #[test]
217    fn test_min_length() {
218        let array = FieldValue::Array(vec![
219            FieldValue::String("item1".to_string()),
220            FieldValue::String("item2".to_string()),
221        ]);
222        
223        assert!(ArrayValidators::min_length(&array, 1).is_ok());
224        assert!(ArrayValidators::min_length(&array, 2).is_ok());
225        assert!(ArrayValidators::min_length(&array, 3).is_err());
226    }
227    
228    #[test]
229    fn test_max_length() {
230        let array = FieldValue::Array(vec![
231            FieldValue::String("item1".to_string()),
232            FieldValue::String("item2".to_string()),
233        ]);
234        
235        assert!(ArrayValidators::max_length(&array, 3).is_ok());
236        assert!(ArrayValidators::max_length(&array, 2).is_ok());
237        assert!(ArrayValidators::max_length(&array, 1).is_err());
238    }
239    
240    #[test]
241    fn test_exact_length() {
242        let array = FieldValue::Array(vec![
243            FieldValue::String("item1".to_string()),
244            FieldValue::String("item2".to_string()),
245        ]);
246        
247        assert!(ArrayValidators::exact_length(&array, 2).is_ok());
248        assert!(ArrayValidators::exact_length(&array, 1).is_err());
249        assert!(ArrayValidators::exact_length(&array, 3).is_err());
250    }
251    
252    #[test]
253    fn test_not_empty() {
254        let empty_array = FieldValue::Array(vec![]);
255        let non_empty_array = FieldValue::Array(vec![FieldValue::String("item".to_string())]);
256        
257        assert!(ArrayValidators::not_empty(&empty_array).is_err());
258        assert!(ArrayValidators::not_empty(&non_empty_array).is_ok());
259    }
260    
261    #[test]
262    fn test_empty() {
263        let empty_array = FieldValue::Array(vec![]);
264        let non_empty_array = FieldValue::Array(vec![FieldValue::String("item".to_string())]);
265        
266        assert!(ArrayValidators::empty(&empty_array).is_ok());
267        assert!(ArrayValidators::empty(&non_empty_array).is_err());
268    }
269    
270    #[test]
271    fn test_length_range() {
272        let array = FieldValue::Array(vec![
273            FieldValue::String("item1".to_string()),
274            FieldValue::String("item2".to_string()),
275        ]);
276        
277        assert!(ArrayValidators::length_range(&array, 1, 3).is_ok());
278        assert!(ArrayValidators::length_range(&array, 2, 2).is_ok());
279        assert!(ArrayValidators::length_range(&array, 3, 5).is_err());
280        assert!(ArrayValidators::length_range(&array, 1, 1).is_err());
281    }
282    
283    #[test]
284    fn test_all_items_not_empty() {
285        let array_with_empty = FieldValue::Array(vec![
286            FieldValue::String("item1".to_string()),
287            FieldValue::String("".to_string()),
288        ]);
289        let array_without_empty = FieldValue::Array(vec![
290            FieldValue::String("item1".to_string()),
291            FieldValue::String("item2".to_string()),
292        ]);
293        
294        assert!(ArrayValidators::all_items_not_empty(&array_with_empty).is_err());
295        assert!(ArrayValidators::all_items_not_empty(&array_without_empty).is_ok());
296    }
297    
298    #[test]
299    fn test_all_items_unique() {
300        let array_with_duplicates = FieldValue::Array(vec![
301            FieldValue::String("item1".to_string()),
302            FieldValue::String("item2".to_string()),
303            FieldValue::String("item1".to_string()),
304        ]);
305        let array_without_duplicates = FieldValue::Array(vec![
306            FieldValue::String("item1".to_string()),
307            FieldValue::String("item2".to_string()),
308            FieldValue::String("item3".to_string()),
309        ]);
310        
311        assert!(ArrayValidators::all_items_unique(&array_with_duplicates).is_err());
312        assert!(ArrayValidators::all_items_unique(&array_without_duplicates).is_ok());
313    }
314    
315    #[test]
316    fn test_all_items_strings() {
317        let array_with_strings = FieldValue::Array(vec![
318            FieldValue::String("item1".to_string()),
319            FieldValue::String("item2".to_string()),
320        ]);
321        let array_with_mixed = FieldValue::Array(vec![
322            FieldValue::String("item1".to_string()),
323            FieldValue::Number(42.0),
324        ]);
325        
326        assert!(ArrayValidators::all_items_strings(&array_with_strings).is_ok());
327        assert!(ArrayValidators::all_items_strings(&array_with_mixed).is_err());
328    }
329    
330    #[test]
331    fn test_contains() {
332        let array = FieldValue::Array(vec![
333            FieldValue::String("item1".to_string()),
334            FieldValue::String("item2".to_string()),
335        ]);
336        
337        assert!(ArrayValidators::contains(&array, |item| {
338            matches!(item, FieldValue::String(s) if s == "item1")
339        }).is_ok());
340        
341        assert!(ArrayValidators::contains(&array, |item| {
342            matches!(item, FieldValue::String(s) if s == "item3")
343        }).is_err());
344    }
345}