Skip to main content

karbon_framework/validation/constraints/collection/
count.rs

1use crate::validation::constraints::{CollectionConstraint, ConstraintResult, ConstraintViolation};
2
3/// Validates that a collection has a count within a given range.
4///
5/// Equivalent to Symfony's `Count` constraint.
6pub struct Count {
7    pub min: Option<usize>,
8    pub max: Option<usize>,
9    pub exact: Option<usize>,
10    pub min_message: String,
11    pub max_message: String,
12    pub exact_message: String,
13}
14
15impl Default for Count {
16    fn default() -> Self {
17        Self {
18            min: None,
19            max: None,
20            exact: None,
21            min_message: "This collection should contain {{ limit }} elements or more.".to_string(),
22            max_message: "This collection should contain {{ limit }} elements or less.".to_string(),
23            exact_message: "This collection should contain exactly {{ limit }} elements.".to_string(),
24        }
25    }
26}
27
28impl Count {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    pub fn min(mut self, min: usize) -> Self {
34        self.min = Some(min);
35        self
36    }
37
38    pub fn max(mut self, max: usize) -> Self {
39        self.max = Some(max);
40        self
41    }
42
43    pub fn exact(mut self, exact: usize) -> Self {
44        self.exact = Some(exact);
45        self
46    }
47
48    pub fn between(min: usize, max: usize) -> Self {
49        Self {
50            min: Some(min),
51            max: Some(max),
52            ..Self::default()
53        }
54    }
55
56    fn format_message(template: &str, limit: usize) -> String {
57        template.replace("{{ limit }}", &limit.to_string())
58    }
59}
60
61impl CollectionConstraint for Count {
62    fn validate_slice<T>(&self, value: &[T]) -> ConstraintResult {
63        let len = value.len();
64
65        if let Some(exact) = self.exact {
66            if len != exact {
67                return Err(ConstraintViolation::new(
68                    self.name(),
69                    Self::format_message(&self.exact_message, exact),
70                    format!("count: {}", len),
71                ));
72            }
73            return Ok(());
74        }
75
76        if let Some(min) = self.min {
77            if len < min {
78                return Err(ConstraintViolation::new(
79                    self.name(),
80                    Self::format_message(&self.min_message, min),
81                    format!("count: {}", len),
82                ));
83            }
84        }
85
86        if let Some(max) = self.max {
87            if len > max {
88                return Err(ConstraintViolation::new(
89                    self.name(),
90                    Self::format_message(&self.max_message, max),
91                    format!("count: {}", len),
92                ));
93            }
94        }
95
96        Ok(())
97    }
98
99    fn name(&self) -> &'static str {
100        "Count"
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_min_count() {
110        let constraint = Count::new().min(2);
111        assert!(constraint.validate_slice(&[1, 2]).is_ok());
112        assert!(constraint.validate_slice(&[1, 2, 3]).is_ok());
113        assert!(constraint.validate_slice(&[1]).is_err());
114    }
115
116    #[test]
117    fn test_max_count() {
118        let constraint = Count::new().max(3);
119        assert!(constraint.validate_slice(&[1, 2, 3]).is_ok());
120        assert!(constraint.validate_slice(&[1]).is_ok());
121        assert!(constraint.validate_slice(&[1, 2, 3, 4]).is_err());
122    }
123
124    #[test]
125    fn test_between_count() {
126        let constraint = Count::between(2, 4);
127        assert!(constraint.validate_slice(&[1, 2]).is_ok());
128        assert!(constraint.validate_slice(&[1, 2, 3, 4]).is_ok());
129        assert!(constraint.validate_slice(&[1]).is_err());
130        assert!(constraint.validate_slice(&[1, 2, 3, 4, 5]).is_err());
131    }
132
133    #[test]
134    fn test_exact_count() {
135        let constraint = Count::new().exact(3);
136        assert!(constraint.validate_slice(&[1, 2, 3]).is_ok());
137        assert!(constraint.validate_slice(&[1, 2]).is_err());
138        assert!(constraint.validate_slice(&[1, 2, 3, 4]).is_err());
139    }
140}