elif_validation/validators/
length.rs1use crate::error::{ValidationError, ValidationResult};
4use crate::traits::ValidationRule;
5use async_trait::async_trait;
6use serde_json::Value;
7
8#[derive(Debug, Clone)]
10pub struct LengthValidator {
11 pub min: Option<usize>,
13 pub max: Option<usize>,
15 pub exact: Option<usize>,
17 pub message: Option<String>,
19}
20
21impl LengthValidator {
22 pub fn new() -> Self {
24 Self {
25 min: None,
26 max: None,
27 exact: None,
28 message: None,
29 }
30 }
31
32 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 {
40 self.max = Some(max);
41 self
42 }
43
44 pub fn exact(mut self, exact: usize) -> Self {
46 self.exact = Some(exact);
47 self
48 }
49
50 pub fn range(mut self, min: usize, max: usize) -> Self {
52 self.min = Some(min);
53 self.max = Some(max);
54 self
55 }
56
57 pub fn message(mut self, message: impl Into<String>) -> Self {
59 self.message = Some(message.into());
60 self
61 }
62
63 fn get_length(&self, value: &Value) -> Option<usize> {
65 match value {
66 Value::String(s) => Some(s.chars().count()), Value::Array(arr) => Some(arr.len()),
68 _ => None,
69 }
70 }
71
72 fn create_error_message(&self, field: &str, actual_length: usize) -> String {
74 if let Some(ref custom_message) = self.message {
75 return custom_message.clone();
76 }
77
78 if let Some(exact) = self.exact {
79 return format!("{} must be exactly {} characters long", field, exact);
80 }
81
82 match (self.min, self.max) {
83 (Some(min), Some(max)) if min == max => {
84 format!("{} must be exactly {} characters long", field, min)
85 }
86 (Some(min), Some(max)) => {
87 format!("{} must be between {} and {} characters long", field, min, max)
88 }
89 (Some(min), None) => {
90 format!("{} must be at least {} characters long", field, min)
91 }
92 (None, Some(max)) => {
93 format!("{} must be at most {} characters long", field, max)
94 }
95 (None, None) => {
96 format!("{} has invalid length: {}", field, actual_length)
97 }
98 }
99 }
100}
101
102impl Default for LengthValidator {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108#[async_trait]
109impl ValidationRule for LengthValidator {
110 async fn validate(&self, value: &Value, field: &str) -> ValidationResult<()> {
111 if value.is_null() {
113 return Ok(());
114 }
115
116 let length = match self.get_length(value) {
117 Some(len) => len,
118 None => {
119 return Err(ValidationError::with_code(
120 field,
121 format!("{} must be a string or array for length validation", field),
122 "invalid_type",
123 ).into());
124 }
125 };
126
127 if let Some(exact) = self.exact {
129 if length != exact {
130 return Err(ValidationError::with_code(
131 field,
132 self.create_error_message(field, length),
133 "length_exact",
134 ).into());
135 }
136 return Ok(());
137 }
138
139 if let Some(min) = self.min {
141 if length < min {
142 return Err(ValidationError::with_code(
143 field,
144 self.create_error_message(field, length),
145 "length_min",
146 ).into());
147 }
148 }
149
150 if let Some(max) = self.max {
152 if length > max {
153 return Err(ValidationError::with_code(
154 field,
155 self.create_error_message(field, length),
156 "length_max",
157 ).into());
158 }
159 }
160
161 Ok(())
162 }
163
164 fn rule_name(&self) -> &'static str {
165 "length"
166 }
167
168 fn parameters(&self) -> Option<Value> {
169 let mut params = serde_json::Map::new();
170
171 if let Some(min) = self.min {
172 params.insert("min".to_string(), Value::Number(serde_json::Number::from(min)));
173 }
174 if let Some(max) = self.max {
175 params.insert("max".to_string(), Value::Number(serde_json::Number::from(max)));
176 }
177 if let Some(exact) = self.exact {
178 params.insert("exact".to_string(), Value::Number(serde_json::Number::from(exact)));
179 }
180 if let Some(ref message) = self.message {
181 params.insert("message".to_string(), Value::String(message.clone()));
182 }
183
184 if params.is_empty() {
185 None
186 } else {
187 Some(Value::Object(params))
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[tokio::test]
197 async fn test_length_validator_min_constraint() {
198 let validator = LengthValidator::new().min(3);
199
200 let result = validator.validate(&Value::String("hi".to_string()), "name").await;
202 assert!(result.is_err());
203
204 let result = validator.validate(&Value::String("bob".to_string()), "name").await;
206 assert!(result.is_ok());
207
208 let result = validator.validate(&Value::String("alice".to_string()), "name").await;
210 assert!(result.is_ok());
211 }
212
213 #[tokio::test]
214 async fn test_length_validator_max_constraint() {
215 let validator = LengthValidator::new().max(5);
216
217 let result = validator.validate(&Value::String("hello".to_string()), "name").await;
219 assert!(result.is_ok());
220
221 let result = validator.validate(&Value::String("hello world".to_string()), "name").await;
223 assert!(result.is_err());
224 }
225
226 #[tokio::test]
227 async fn test_length_validator_exact_constraint() {
228 let validator = LengthValidator::new().exact(4);
229
230 let result = validator.validate(&Value::String("test".to_string()), "code").await;
232 assert!(result.is_ok());
233
234 let result = validator.validate(&Value::String("hi".to_string()), "code").await;
236 assert!(result.is_err());
237
238 let result = validator.validate(&Value::String("testing".to_string()), "code").await;
240 assert!(result.is_err());
241 }
242
243 #[tokio::test]
244 async fn test_length_validator_range() {
245 let validator = LengthValidator::new().range(3, 10);
246
247 let result = validator.validate(&Value::String("hi".to_string()), "password").await;
249 assert!(result.is_err());
250
251 let result = validator.validate(&Value::String("secret".to_string()), "password").await;
253 assert!(result.is_ok());
254
255 let result = validator.validate(&Value::String("very_long_password".to_string()), "password").await;
257 assert!(result.is_err());
258 }
259
260 #[tokio::test]
261 async fn test_length_validator_with_arrays() {
262 let validator = LengthValidator::new().min(2).max(4);
263
264 let result = validator.validate(&Value::Array(vec![Value::String("item1".to_string())]), "tags").await;
266 assert!(result.is_err());
267
268 let result = validator.validate(&Value::Array(vec![
270 Value::String("tag1".to_string()),
271 Value::String("tag2".to_string()),
272 ]), "tags").await;
273 assert!(result.is_ok());
274
275 let result = validator.validate(&Value::Array(vec![
277 Value::String("tag1".to_string()),
278 Value::String("tag2".to_string()),
279 Value::String("tag3".to_string()),
280 Value::String("tag4".to_string()),
281 Value::String("tag5".to_string()),
282 ]), "tags").await;
283 assert!(result.is_err());
284 }
285
286 #[tokio::test]
287 async fn test_length_validator_unicode_support() {
288 let validator = LengthValidator::new().max(5);
289
290 let result = validator.validate(&Value::String("café".to_string()), "name").await;
292 assert!(result.is_ok());
293
294 let result = validator.validate(&Value::String("🦀🚀✨".to_string()), "emoji").await;
295 assert!(result.is_ok());
296
297 let result = validator.validate(&Value::String("🦀🚀✨🎉🔥💯".to_string()), "emoji").await;
299 assert!(result.is_err());
300 }
301
302 #[tokio::test]
303 async fn test_length_validator_with_null() {
304 let validator = LengthValidator::new().min(1);
305
306 let result = validator.validate(&Value::Null, "optional_field").await;
308 assert!(result.is_ok());
309 }
310
311 #[tokio::test]
312 async fn test_length_validator_invalid_type() {
313 let validator = LengthValidator::new().min(1);
314
315 let result = validator.validate(&Value::Number(serde_json::Number::from(42)), "age").await;
317 assert!(result.is_err());
318
319 let errors = result.unwrap_err();
320 let field_errors = errors.get_field_errors("age").unwrap();
321 assert_eq!(field_errors[0].code, "invalid_type");
322 }
323
324 #[tokio::test]
325 async fn test_length_validator_custom_message() {
326 let validator = LengthValidator::new()
327 .min(8)
328 .message("Password must be strong");
329
330 let result = validator.validate(&Value::String("weak".to_string()), "password").await;
331 assert!(result.is_err());
332
333 let errors = result.unwrap_err();
334 let field_errors = errors.get_field_errors("password").unwrap();
335 assert_eq!(field_errors[0].message, "Password must be strong");
336 }
337}