skp_validator_rules/string/
length.rs1use skp_validator_core::{Rule, ValidationContext, ValidationErrors, ValidationError, ValidationResult};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum LengthMode {
8 #[default]
10 Chars,
11 Bytes,
13 Graphemes,
15}
16
17#[derive(Debug, Clone)]
32pub struct LengthRule {
33 pub min: Option<usize>,
35 pub max: Option<usize>,
37 pub equal: Option<usize>,
39 pub mode: LengthMode,
41 pub message: Option<String>,
43}
44
45impl LengthRule {
46 pub fn new() -> Self {
48 Self {
49 min: None,
50 max: None,
51 equal: None,
52 mode: LengthMode::default(),
53 message: None,
54 }
55 }
56
57 pub fn min(mut self, min: usize) -> Self {
59 self.min = Some(min);
60 self
61 }
62
63 pub fn max(mut self, max: usize) -> Self {
65 self.max = Some(max);
66 self
67 }
68
69 pub fn equal(mut self, len: usize) -> Self {
71 self.equal = Some(len);
72 self
73 }
74
75 pub fn mode(mut self, mode: LengthMode) -> Self {
77 self.mode = mode;
78 self
79 }
80
81 pub fn message(mut self, msg: impl Into<String>) -> Self {
83 self.message = Some(msg.into());
84 self
85 }
86
87 fn calculate_length(&self, s: &str) -> usize {
89 match self.mode {
90 LengthMode::Chars => s.chars().count(),
91 LengthMode::Bytes => s.len(),
92 LengthMode::Graphemes => {
93 s.chars().count()
96 }
97 }
98 }
99}
100
101impl Default for LengthRule {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl Rule<str> for LengthRule {
108 fn validate(&self, value: &str, _ctx: &ValidationContext) -> ValidationResult<()> {
109 let len = self.calculate_length(value);
110
111 if let Some(exact) = self.equal
113 && len != exact
114 {
115 let msg = self.message.clone().unwrap_or_else(|| {
116 format!("Must be exactly {} characters", exact)
117 });
118 return Err(ValidationErrors::from_iter([
119 ValidationError::root("length.equal", msg)
120 .with_param("expected", exact as i64)
121 .with_param("actual", len as i64)
122 ]));
123 }
124
125 if let Some(min) = self.min
127 && len < min
128 {
129 let msg = self.message.clone().unwrap_or_else(|| {
130 format!("Must be at least {} characters", min)
131 });
132 return Err(ValidationErrors::from_iter([
133 ValidationError::root("length.min", msg)
134 .with_param("min", min as i64)
135 .with_param("actual", len as i64)
136 ]));
137 }
138
139 if let Some(max) = self.max
141 && len > max
142 {
143 let msg = self.message.clone().unwrap_or_else(|| {
144 format!("Must be at most {} characters", max)
145 });
146 return Err(ValidationErrors::from_iter([
147 ValidationError::root("length.max", msg)
148 .with_param("max", max as i64)
149 .with_param("actual", len as i64)
150 ]));
151 }
152
153 Ok(())
154 }
155
156 fn name(&self) -> &'static str {
157 "length"
158 }
159
160 fn default_message(&self) -> String {
161 if let Some(exact) = self.equal {
162 format!("Must be exactly {} characters", exact)
163 } else {
164 match (self.min, self.max) {
165 (Some(min), Some(max)) => format!("Must be between {} and {} characters", min, max),
166 (Some(min), None) => format!("Must be at least {} characters", min),
167 (None, Some(max)) => format!("Must be at most {} characters", max),
168 (None, None) => "Invalid length".to_string(),
169 }
170 }
171 }
172}
173
174impl Rule<String> for LengthRule {
175 fn validate(&self, value: &String, ctx: &ValidationContext) -> ValidationResult<()> {
176 <Self as Rule<str>>::validate(self, value.as_str(), ctx)
177 }
178
179 fn name(&self) -> &'static str {
180 "length"
181 }
182
183 fn default_message(&self) -> String {
184 <Self as Rule<str>>::default_message(self)
185 }
186}
187
188impl<T> Rule<Vec<T>> for LengthRule {
190 fn validate(&self, value: &Vec<T>, _ctx: &ValidationContext) -> ValidationResult<()> {
191 let len = value.len();
192
193 if let Some(exact) = self.equal
194 && len != exact
195 {
196 let msg = self.message.clone().unwrap_or_else(|| {
197 format!("Must have exactly {} items", exact)
198 });
199 return Err(ValidationErrors::from_iter([
200 ValidationError::root("length.equal", msg)
201 .with_param("expected", exact as i64)
202 .with_param("actual", len as i64)
203 ]));
204 }
205
206 if let Some(min) = self.min
207 && len < min
208 {
209 let msg = self.message.clone().unwrap_or_else(|| {
210 format!("Must have at least {} items", min)
211 });
212 return Err(ValidationErrors::from_iter([
213 ValidationError::root("length.min", msg)
214 .with_param("min", min as i64)
215 .with_param("actual", len as i64)
216 ]));
217 }
218
219 if let Some(max) = self.max
220 && len > max
221 {
222 let msg = self.message.clone().unwrap_or_else(|| {
223 format!("Must have at most {} items", max)
224 });
225 return Err(ValidationErrors::from_iter([
226 ValidationError::root("length.max", msg)
227 .with_param("max", max as i64)
228 .with_param("actual", len as i64)
229 ]));
230 }
231
232 Ok(())
233 }
234
235 fn name(&self) -> &'static str {
236 "length"
237 }
238
239 fn default_message(&self) -> String {
240 "Invalid length".to_string()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_min_length() {
250 let rule = LengthRule::new().min(3);
251 let ctx = ValidationContext::default();
252
253 assert!(rule.validate("abc", &ctx).is_ok());
254 assert!(rule.validate("abcd", &ctx).is_ok());
255 assert!(rule.validate("ab", &ctx).is_err());
256 }
257
258 #[test]
259 fn test_max_length() {
260 let rule = LengthRule::new().max(5);
261 let ctx = ValidationContext::default();
262
263 assert!(rule.validate("abc", &ctx).is_ok());
264 assert!(rule.validate("abcde", &ctx).is_ok());
265 assert!(rule.validate("abcdef", &ctx).is_err());
266 }
267
268 #[test]
269 fn test_range() {
270 let rule = LengthRule::new().min(3).max(5);
271 let ctx = ValidationContext::default();
272
273 assert!(rule.validate("ab", &ctx).is_err());
274 assert!(rule.validate("abc", &ctx).is_ok());
275 assert!(rule.validate("abcde", &ctx).is_ok());
276 assert!(rule.validate("abcdef", &ctx).is_err());
277 }
278
279 #[test]
280 fn test_exact() {
281 let rule = LengthRule::new().equal(5);
282 let ctx = ValidationContext::default();
283
284 assert!(rule.validate("abcd", &ctx).is_err());
285 assert!(rule.validate("abcde", &ctx).is_ok());
286 assert!(rule.validate("abcdef", &ctx).is_err());
287 }
288
289 #[test]
290 fn test_vec_length() {
291 let rule = LengthRule::new().min(2).max(4);
292 let ctx = ValidationContext::default();
293
294 assert!(rule.validate(&vec![1], &ctx).is_err());
295 assert!(rule.validate(&vec![1, 2], &ctx).is_ok());
296 assert!(rule.validate(&vec![1, 2, 3, 4], &ctx).is_ok());
297 assert!(rule.validate(&vec![1, 2, 3, 4, 5], &ctx).is_err());
298 }
299}