1use crate::error::{ValidationError, ValidationResult};
4use crate::traits::ValidationRule;
5use async_trait::async_trait;
6use serde_json::Value;
7use std::sync::Arc;
8
9pub type SyncValidationFn = Arc<dyn Fn(&Value, &str) -> ValidationResult<()> + Send + Sync>;
11
12#[derive(Clone)]
14pub struct CustomValidator {
15 pub name: String,
17 sync_validator: Option<SyncValidationFn>,
19 pub message: Option<String>,
21}
22
23impl CustomValidator {
24 pub fn new<F>(name: impl Into<String>, validator: F) -> Self
26 where
27 F: Fn(&Value, &str) -> ValidationResult<()> + Send + Sync + 'static,
28 {
29 Self {
30 name: name.into(),
31 sync_validator: Some(Arc::new(validator)),
32 message: None,
33 }
34 }
35
36 pub fn message(mut self, message: impl Into<String>) -> Self {
38 self.message = Some(message.into());
39 self
40 }
41
42 pub fn name(&self) -> &str {
44 &self.name
45 }
46}
47
48impl std::fmt::Debug for CustomValidator {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("CustomValidator")
51 .field("name", &self.name)
52 .field("has_sync_validator", &self.sync_validator.is_some())
53 .field("message", &self.message)
54 .finish()
55 }
56}
57
58#[async_trait]
59impl ValidationRule for CustomValidator {
60 async fn validate(&self, value: &Value, field: &str) -> ValidationResult<()> {
61 if value.is_null() {
63 return Ok(());
64 }
65
66 let result = if let Some(ref sync_validator) = self.sync_validator {
67 sync_validator(value, field)
68 } else {
69 return Err(ValidationError::with_code(
71 field,
72 "Custom validator has no validation function",
73 "no_validator",
74 ).into());
75 };
76
77 match (result, &self.message) {
79 (Err(mut errors), Some(custom_message)) => {
80 for field_errors in errors.errors.values_mut() {
82 for error in field_errors {
83 error.message = custom_message.clone();
84 }
85 }
86 Err(errors)
87 }
88 (result, _) => result,
89 }
90 }
91
92 fn rule_name(&self) -> &'static str {
93 "custom"
94 }
95
96 fn parameters(&self) -> Option<Value> {
97 let mut params = serde_json::Map::new();
98
99 params.insert("name".to_string(), Value::String(self.name.clone()));
100
101 if let Some(ref message) = self.message {
102 params.insert("message".to_string(), Value::String(message.clone()));
103 }
104
105 Some(Value::Object(params))
106 }
107}
108
109impl CustomValidator {
111 pub fn one_of(name: impl Into<String>, allowed_values: Vec<String>) -> Self {
113 let allowed = allowed_values.clone();
114 Self::new(name, move |value, field| {
115 if let Some(string_value) = value.as_str() {
116 if allowed.contains(&string_value.to_string()) {
117 Ok(())
118 } else {
119 Err(ValidationError::with_code(
120 field,
121 format!("{} must be one of: {}", field, allowed.join(", ")),
122 "not_in_list",
123 ).into())
124 }
125 } else {
126 Err(ValidationError::with_code(
127 field,
128 format!("{} must be a string", field),
129 "invalid_type",
130 ).into())
131 }
132 })
133 }
134
135 pub fn not_one_of(name: impl Into<String>, forbidden_values: Vec<String>) -> Self {
137 let forbidden = forbidden_values.clone();
138 Self::new(name, move |value, field| {
139 if let Some(string_value) = value.as_str() {
140 if forbidden.contains(&string_value.to_string()) {
141 Err(ValidationError::with_code(
142 field,
143 format!("{} cannot be one of: {}", field, forbidden.join(", ")),
144 "in_forbidden_list",
145 ).into())
146 } else {
147 Ok(())
148 }
149 } else {
150 Ok(()) }
152 })
153 }
154
155 pub fn contains(name: impl Into<String>, substring: String) -> Self {
157 Self::new(name, move |value, field| {
158 if let Some(string_value) = value.as_str() {
159 if string_value.contains(&substring) {
160 Ok(())
161 } else {
162 Err(ValidationError::with_code(
163 field,
164 format!("{} must contain '{}'", field, substring),
165 "missing_substring",
166 ).into())
167 }
168 } else {
169 Err(ValidationError::with_code(
170 field,
171 format!("{} must be a string", field),
172 "invalid_type",
173 ).into())
174 }
175 })
176 }
177
178 pub fn not_contains(name: impl Into<String>, substring: String) -> Self {
180 Self::new(name, move |value, field| {
181 if let Some(string_value) = value.as_str() {
182 if !string_value.contains(&substring) {
183 Ok(())
184 } else {
185 Err(ValidationError::with_code(
186 field,
187 format!("{} must not contain '{}'", field, substring),
188 "forbidden_substring",
189 ).into())
190 }
191 } else {
192 Ok(()) }
194 })
195 }
196
197 pub fn starts_with(name: impl Into<String>, prefix: String) -> Self {
199 Self::new(name, move |value, field| {
200 if let Some(string_value) = value.as_str() {
201 if string_value.starts_with(&prefix) {
202 Ok(())
203 } else {
204 Err(ValidationError::with_code(
205 field,
206 format!("{} must start with '{}'", field, prefix),
207 "invalid_prefix",
208 ).into())
209 }
210 } else {
211 Err(ValidationError::with_code(
212 field,
213 format!("{} must be a string", field),
214 "invalid_type",
215 ).into())
216 }
217 })
218 }
219
220 pub fn ends_with(name: impl Into<String>, suffix: String) -> Self {
222 Self::new(name, move |value, field| {
223 if let Some(string_value) = value.as_str() {
224 if string_value.ends_with(&suffix) {
225 Ok(())
226 } else {
227 Err(ValidationError::with_code(
228 field,
229 format!("{} must end with '{}'", field, suffix),
230 "invalid_suffix",
231 ).into())
232 }
233 } else {
234 Err(ValidationError::with_code(
235 field,
236 format!("{} must be a string", field),
237 "invalid_type",
238 ).into())
239 }
240 })
241 }
242
243 pub fn array_length(name: impl Into<String>, expected_length: usize) -> Self {
245 Self::new(name, move |value, field| {
246 if let Some(array) = value.as_array() {
247 if array.len() == expected_length {
248 Ok(())
249 } else {
250 Err(ValidationError::with_code(
251 field,
252 format!("{} must have exactly {} items", field, expected_length),
253 "invalid_array_length",
254 ).into())
255 }
256 } else {
257 Err(ValidationError::with_code(
258 field,
259 format!("{} must be an array", field),
260 "invalid_type",
261 ).into())
262 }
263 })
264 }
265
266 pub fn array_all<F>(name: impl Into<String>, condition: F) -> Self
268 where
269 F: Fn(&Value) -> bool + Send + Sync + 'static,
270 {
271 Self::new(name, move |value, field| {
272 if let Some(array) = value.as_array() {
273 for (index, item) in array.iter().enumerate() {
274 if !condition(item) {
275 return Err(ValidationError::with_code(
276 field,
277 format!("{} item at index {} does not meet the required condition", field, index),
278 "array_condition_failed",
279 ).into());
280 }
281 }
282 Ok(())
283 } else {
284 Err(ValidationError::with_code(
285 field,
286 format!("{} must be an array", field),
287 "invalid_type",
288 ).into())
289 }
290 })
291 }
292
293 }
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[tokio::test]
302 async fn test_custom_validator_sync() {
303 let validator = CustomValidator::new("even_number", |value, field| {
304 if let Some(num) = value.as_i64() {
305 if num % 2 == 0 {
306 Ok(())
307 } else {
308 Err(ValidationError::new(field, "Must be an even number").into())
309 }
310 } else {
311 Err(ValidationError::new(field, "Must be a number").into())
312 }
313 });
314
315 let result = validator.validate(&Value::Number(serde_json::Number::from(4)), "count").await;
317 assert!(result.is_ok());
318
319 let result = validator.validate(&Value::Number(serde_json::Number::from(5)), "count").await;
321 assert!(result.is_err());
322 }
323
324 #[tokio::test]
327 async fn test_custom_validator_one_of() {
328 let validator = CustomValidator::one_of(
329 "status_validator",
330 vec!["active".to_string(), "inactive".to_string(), "pending".to_string()]
331 );
332
333 assert!(validator.validate(&Value::String("active".to_string()), "status").await.is_ok());
335 assert!(validator.validate(&Value::String("pending".to_string()), "status").await.is_ok());
336
337 assert!(validator.validate(&Value::String("unknown".to_string()), "status").await.is_err());
339 }
340
341 #[tokio::test]
342 async fn test_custom_validator_not_one_of() {
343 let validator = CustomValidator::not_one_of(
344 "username_validator",
345 vec!["admin".to_string(), "root".to_string(), "system".to_string()]
346 );
347
348 assert!(validator.validate(&Value::String("john".to_string()), "username").await.is_ok());
350 assert!(validator.validate(&Value::String("alice".to_string()), "username").await.is_ok());
351
352 assert!(validator.validate(&Value::String("admin".to_string()), "username").await.is_err());
354 assert!(validator.validate(&Value::String("root".to_string()), "username").await.is_err());
355 }
356
357 #[tokio::test]
358 async fn test_custom_validator_contains() {
359 let validator = CustomValidator::contains("email_domain", "@company.com".to_string());
360
361 assert!(validator.validate(&Value::String("john@company.com".to_string()), "email").await.is_ok());
363
364 assert!(validator.validate(&Value::String("john@gmail.com".to_string()), "email").await.is_err());
366 }
367
368 #[tokio::test]
369 async fn test_custom_validator_starts_with() {
370 let validator = CustomValidator::starts_with("api_key", "sk_".to_string());
371
372 assert!(validator.validate(&Value::String("sk_1234567890".to_string()), "api_key").await.is_ok());
374
375 assert!(validator.validate(&Value::String("pk_1234567890".to_string()), "api_key").await.is_err());
377 }
378
379 #[tokio::test]
380 async fn test_custom_validator_ends_with() {
381 let validator = CustomValidator::ends_with("image_file", ".jpg".to_string());
382
383 assert!(validator.validate(&Value::String("photo.jpg".to_string()), "filename").await.is_ok());
385
386 assert!(validator.validate(&Value::String("photo.png".to_string()), "filename").await.is_err());
388 }
389
390 #[tokio::test]
391 async fn test_custom_validator_array_length() {
392 let validator = CustomValidator::array_length("tags", 3);
393
394 let array = Value::Array(vec![
396 Value::String("tag1".to_string()),
397 Value::String("tag2".to_string()),
398 Value::String("tag3".to_string()),
399 ]);
400 assert!(validator.validate(&array, "tags").await.is_ok());
401
402 let array = Value::Array(vec![
404 Value::String("tag1".to_string()),
405 Value::String("tag2".to_string()),
406 ]);
407 assert!(validator.validate(&array, "tags").await.is_err());
408 }
409
410 #[tokio::test]
411 async fn test_custom_validator_array_all() {
412 let validator = CustomValidator::array_all("numbers", |value| {
413 value.as_i64().map_or(false, |n| n > 0)
414 });
415
416 let array = Value::Array(vec![
418 Value::Number(serde_json::Number::from(1)),
419 Value::Number(serde_json::Number::from(2)),
420 Value::Number(serde_json::Number::from(3)),
421 ]);
422 assert!(validator.validate(&array, "numbers").await.is_ok());
423
424 let array = Value::Array(vec![
426 Value::Number(serde_json::Number::from(1)),
427 Value::Number(serde_json::Number::from(-2)),
428 Value::Number(serde_json::Number::from(3)),
429 ]);
430 assert!(validator.validate(&array, "numbers").await.is_err());
431 }
432
433 #[tokio::test]
436 async fn test_custom_validator_with_custom_message() {
437 let validator = CustomValidator::new("always_fail", |_value, field| {
438 Err(ValidationError::new(field, "Original message").into())
439 }).message("Custom error message");
440
441 let result = validator.validate(&Value::String("test".to_string()), "field").await;
442 assert!(result.is_err());
443
444 let errors = result.unwrap_err();
445 let field_errors = errors.get_field_errors("field").unwrap();
446 assert_eq!(field_errors[0].message, "Custom error message");
447 }
448
449 #[tokio::test]
450 async fn test_custom_validator_with_null() {
451 let validator = CustomValidator::new("not_null", |value, field| {
452 if value.is_null() {
453 Err(ValidationError::new(field, "Cannot be null").into())
454 } else {
455 Ok(())
456 }
457 });
458
459 let result = validator.validate(&Value::Null, "field").await;
461 assert!(result.is_ok());
462 }
463}