elif_validation/
rules.rs

1//! Validation rules builder and composition system
2
3use crate::error::{ValidationErrors, ValidationResult};
4use crate::traits::{ValidateField, ValidateRequest, ValidationRule};
5use crate::validators::*;
6use async_trait::async_trait;
7use serde_json::Value;
8use std::collections::HashMap;
9use std::sync::Arc;
10use service_builder::builder;
11
12/// Collection of validation rules for a specific field or request
13#[derive(Clone)]
14pub struct Rules {
15    /// Field-level validation rules
16    field_rules: HashMap<String, Vec<Arc<dyn ValidationRule>>>,
17    /// Global request-level validation rules
18    request_rules: Vec<Arc<dyn ValidationRule>>,
19}
20
21impl std::fmt::Debug for Rules {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        f.debug_struct("Rules")
24            .field("field_rules_count", &self.field_rules.len())
25            .field("request_rules_count", &self.request_rules.len())
26            .field("validated_fields", &self.get_validated_fields())
27            .finish()
28    }
29}
30
31impl Rules {
32    /// Create a new empty rules collection
33    pub fn new() -> Self {
34        Self {
35            field_rules: HashMap::new(),
36            request_rules: Vec::new(),
37        }
38    }
39
40    /// Add a validation rule for a specific field
41    pub fn field<R>(mut self, field: impl Into<String>, rule: R) -> Self
42    where
43        R: ValidationRule + 'static,
44    {
45        let field = field.into();
46        self.field_rules
47            .entry(field)
48            .or_default()
49            .push(Arc::new(rule));
50        self
51    }
52
53    /// Add multiple validation rules for a specific field
54    pub fn field_rules<R>(mut self, field: impl Into<String>, rules: Vec<R>) -> Self
55    where
56        R: ValidationRule + 'static,
57    {
58        let field = field.into();
59        let rule_arcs: Vec<Arc<dyn ValidationRule>> = rules
60            .into_iter()
61            .map(|r| Arc::new(r) as Arc<dyn ValidationRule>)
62            .collect();
63        
64        self.field_rules
65            .entry(field)
66            .or_default()
67            .extend(rule_arcs);
68        self
69    }
70
71    /// Add a request-level validation rule (cross-field validation)
72    pub fn request<R>(mut self, rule: R) -> Self
73    where
74        R: ValidationRule + 'static,
75    {
76        self.request_rules.push(Arc::new(rule));
77        self
78    }
79
80    /// Get rules for a specific field
81    pub fn get_field_rules(&self, field: &str) -> Option<&Vec<Arc<dyn ValidationRule>>> {
82        self.field_rules.get(field)
83    }
84
85    /// Get all request-level rules
86    pub fn get_request_rules(&self) -> &Vec<Arc<dyn ValidationRule>> {
87        &self.request_rules
88    }
89
90    /// Check if there are any rules defined
91    pub fn is_empty(&self) -> bool {
92        self.field_rules.is_empty() && self.request_rules.is_empty()
93    }
94
95    /// Get the number of field rules
96    pub fn field_rule_count(&self) -> usize {
97        self.field_rules.len()
98    }
99
100    /// Get the number of request rules
101    pub fn request_rule_count(&self) -> usize {
102        self.request_rules.len()
103    }
104
105    /// Get all field names that have validation rules
106    pub fn get_validated_fields(&self) -> Vec<&String> {
107        self.field_rules.keys().collect()
108    }
109}
110
111impl Default for Rules {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117#[async_trait]
118impl ValidateField for Rules {
119    async fn validate_field(&self, field: &str, value: &Value) -> ValidationResult<()> {
120        if let Some(rules) = self.field_rules.get(field) {
121            let mut errors = ValidationErrors::new();
122            
123            for rule in rules {
124                if let Err(rule_errors) = rule.validate(value, field).await {
125                    errors.merge(rule_errors);
126                }
127            }
128            
129            if errors.is_empty() {
130                Ok(())
131            } else {
132                Err(errors)
133            }
134        } else {
135            // No rules defined for this field - consider it valid
136            Ok(())
137        }
138    }
139}
140
141#[async_trait]
142impl ValidateRequest for Rules {
143    async fn validate_request(&self, data: &HashMap<String, Value>) -> ValidationResult<()> {
144        let mut errors = ValidationErrors::new();
145        
146        // Apply request-level validation rules
147        for rule in &self.request_rules {
148            // For request-level rules, we pass the entire data as a JSON object
149            let data_value = Value::Object(
150                data.iter()
151                    .map(|(k, v)| (k.clone(), v.clone()))
152                    .collect::<serde_json::Map<String, Value>>()
153            );
154            
155            if let Err(rule_errors) = rule.validate(&data_value, "request").await {
156                errors.merge(rule_errors);
157            }
158        }
159        
160        if errors.is_empty() {
161            Ok(())
162        } else {
163            Err(errors)
164        }
165    }
166}
167
168/// Configuration for Rules builder - contains the accumulated rules
169#[derive(Clone)]
170#[builder]
171pub struct RulesBuilderConfig {
172    #[builder(default)]
173    pub field_rules: HashMap<String, Vec<Arc<dyn ValidationRule>>>,
174    
175    #[builder(default)]
176    pub request_rules: Vec<Arc<dyn ValidationRule>>,
177}
178
179impl std::fmt::Debug for RulesBuilderConfig {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        f.debug_struct("RulesBuilderConfig")
182            .field("field_rules_count", &self.field_rules.len())
183            .field("request_rules_count", &self.request_rules.len())
184            .finish()
185    }
186}
187
188impl RulesBuilderConfig {
189    /// Build a Rules from the builder config
190    pub fn build_rules(self) -> Rules {
191        Rules {
192            field_rules: self.field_rules,
193            request_rules: self.request_rules,
194        }
195    }
196}
197
198// Add convenience methods to the generated builder
199impl RulesBuilderConfigBuilder {
200    /// Add a validation rule for a specific field
201    pub fn field_rule<R>(self, field: impl Into<String>, rule: R) -> Self
202    where
203        R: ValidationRule + 'static,
204    {
205        let field = field.into();
206        let mut field_rules = self.field_rules.unwrap_or_default();
207        field_rules
208            .entry(field)
209            .or_default()
210            .push(Arc::new(rule));
211        RulesBuilderConfigBuilder {
212            field_rules: Some(field_rules),
213            request_rules: self.request_rules,
214        }
215    }
216    
217    /// Add multiple validation rules for a specific field
218    pub fn field_rules_vec<R>(self, field: impl Into<String>, rules: Vec<R>) -> Self
219    where
220        R: ValidationRule + 'static,
221    {
222        let field = field.into();
223        let rule_arcs: Vec<Arc<dyn ValidationRule>> = rules
224            .into_iter()
225            .map(|r| Arc::new(r) as Arc<dyn ValidationRule>)
226            .collect();
227        
228        let mut field_rules = self.field_rules.unwrap_or_default();
229        field_rules
230            .entry(field)
231            .or_default()
232            .extend(rule_arcs);
233        RulesBuilderConfigBuilder {
234            field_rules: Some(field_rules),
235            request_rules: self.request_rules,
236        }
237    }
238    
239    /// Add a request-level validation rule
240    pub fn request_rule<R>(self, rule: R) -> Self
241    where
242        R: ValidationRule + 'static,
243    {
244        let mut request_rules = self.request_rules.unwrap_or_default();
245        request_rules.push(Arc::new(rule));
246        RulesBuilderConfigBuilder {
247            field_rules: self.field_rules,
248            request_rules: Some(request_rules),
249        }
250    }
251    
252    pub fn build_config(self) -> RulesBuilderConfig {
253        self.build_with_defaults().unwrap()
254    }
255}
256
257/// Builder for creating common validation rule combinations
258pub struct RulesBuilder {
259    builder_config: RulesBuilderConfigBuilder,
260}
261
262impl RulesBuilder {
263    /// Create a new rules builder
264    pub fn new() -> Self {
265        Self {
266            builder_config: RulesBuilderConfig::builder(),
267        }
268    }
269
270    /// Build and return the rules
271    pub fn build(self) -> Rules {
272        self.builder_config.build_config().build_rules()
273    }
274
275    /// Add validation rules for a required string field
276    pub fn required_string(
277        mut self, 
278        field: impl Into<String>, 
279        min_length: Option<usize>, 
280        max_length: Option<usize>
281    ) -> Self {
282        let field = field.into();
283        
284        // Add required validator
285        self.builder_config = self.builder_config.field_rule(field.clone(), RequiredValidator::new());
286        
287        // Add length validator if constraints are specified
288        if min_length.is_some() || max_length.is_some() {
289            let mut length_validator = LengthValidator::new();
290            if let Some(min) = min_length {
291                length_validator = length_validator.min(min);
292            }
293            if let Some(max) = max_length {
294                length_validator = length_validator.max(max);
295            }
296            self.builder_config = self.builder_config.field_rule(field, length_validator);
297        }
298        
299        self
300    }
301
302    /// Add validation rules for a required email field
303    pub fn required_email(mut self, field: impl Into<String>) -> Self {
304        let field = field.into();
305        
306        self.builder_config = self.builder_config
307            .field_rule(field.clone(), RequiredValidator::new())
308            .field_rule(field, EmailValidator::new());
309        
310        self
311    }
312
313    /// Add validation rules for an optional email field
314    pub fn optional_email(mut self, field: impl Into<String>) -> Self {
315        let field = field.into();
316        
317        // Only add email validation - no required validation
318        self.builder_config = self.builder_config.field_rule(field, EmailValidator::new());
319        
320        self
321    }
322
323    /// Add validation rules for a required numeric field
324    pub fn required_number(
325        mut self, 
326        field: impl Into<String>, 
327        min: Option<f64>, 
328        max: Option<f64>
329    ) -> Self {
330        let field = field.into();
331        
332        self.builder_config = self.builder_config.field_rule(field.clone(), RequiredValidator::new());
333        
334        let mut numeric_validator = NumericValidator::new();
335        if let Some(min_val) = min {
336            numeric_validator = numeric_validator.min(min_val);
337        }
338        if let Some(max_val) = max {
339            numeric_validator = numeric_validator.max(max_val);
340        }
341        
342        self.builder_config = self.builder_config.field_rule(field, numeric_validator);
343        
344        self
345    }
346
347    /// Add validation rules for a required integer field
348    pub fn required_integer(
349        mut self, 
350        field: impl Into<String>, 
351        min: Option<f64>, 
352        max: Option<f64>
353    ) -> Self {
354        let field = field.into();
355        
356        self.builder_config = self.builder_config.field_rule(field.clone(), RequiredValidator::new());
357        
358        let mut numeric_validator = NumericValidator::new().integer_only(true);
359        if let Some(min_val) = min {
360            numeric_validator = numeric_validator.min(min_val);
361        }
362        if let Some(max_val) = max {
363            numeric_validator = numeric_validator.max(max_val);
364        }
365        
366        self.builder_config = self.builder_config.field_rule(field, numeric_validator);
367        
368        self
369    }
370
371    /// Add validation rules for a field that must match a pattern
372    pub fn pattern(mut self, field: impl Into<String>, pattern: &str) -> Self {
373        let field = field.into();
374        
375        if let Ok(pattern_validator) = PatternValidator::new(pattern) {
376            self.builder_config = self.builder_config.field_rule(field, pattern_validator);
377        }
378        
379        self
380    }
381
382    /// Add validation rules for a field that must be one of the allowed values
383    pub fn one_of(mut self, field: impl Into<String>, allowed_values: Vec<String>) -> Self {
384        let field = field.into();
385        
386        let custom_validator = CustomValidator::one_of(
387            format!("{}_one_of", field),
388            allowed_values
389        );
390        
391        self.builder_config = self.builder_config.field_rule(field, custom_validator);
392        
393        self
394    }
395
396    /// Add a custom validation rule
397    pub fn custom<R>(mut self, field: impl Into<String>, rule: R) -> Self
398    where
399        R: ValidationRule + 'static,
400    {
401        self.builder_config = self.builder_config.field_rule(field, rule);
402        self
403    }
404
405    /// Add a request-level validation rule
406    pub fn request_rule<R>(mut self, rule: R) -> Self
407    where
408        R: ValidationRule + 'static,
409    {
410        self.builder_config = self.builder_config.request_rule(rule);
411        self
412    }
413}
414
415impl Default for RulesBuilder {
416    fn default() -> Self {
417        Self::new()
418    }
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424    use crate::error::ValidationError;
425    use crate::traits::Validate;
426
427    #[tokio::test]
428    async fn test_rules_field_validation() {
429        let rules = Rules::new()
430            .field("name", RequiredValidator::new())
431            .field("name", LengthValidator::new().min(2).max(50))
432            .field("email", EmailValidator::new());
433
434        // Valid name
435        let result = rules.validate_field("name", &Value::String("John".to_string())).await;
436        assert!(result.is_ok());
437
438        // Invalid name (too short)
439        let result = rules.validate_field("name", &Value::String("J".to_string())).await;
440        assert!(result.is_err());
441
442        // Valid email
443        let result = rules.validate_field("email", &Value::String("john@example.com".to_string())).await;
444        assert!(result.is_ok());
445
446        // Invalid email
447        let result = rules.validate_field("email", &Value::String("not-an-email".to_string())).await;
448        assert!(result.is_err());
449    }
450
451    #[tokio::test]
452    async fn test_rules_request_validation() {
453        let password_confirmation_rule = CustomValidator::new("password_confirmation", |value, _field| {
454            if let Some(obj) = value.as_object() {
455                let password = obj.get("password").and_then(|v| v.as_str());
456                let confirmation = obj.get("password_confirmation").and_then(|v| v.as_str());
457                
458                match (password, confirmation) {
459                    (Some(pwd), Some(conf)) if pwd == conf => Ok(()),
460                    (Some(_), Some(_)) => Err(ValidationError::new("password_confirmation", "Passwords do not match").into()),
461                    (Some(_), None) => Err(ValidationError::new("password_confirmation", "Password confirmation is required").into()),
462                    _ => Ok(()), // No password field, skip validation
463                }
464            } else {
465                Ok(())
466            }
467        });
468
469        let rules = Rules::new().request(password_confirmation_rule);
470
471        // Valid matching passwords
472        let mut data = HashMap::new();
473        data.insert("password".to_string(), Value::String("secret123".to_string()));
474        data.insert("password_confirmation".to_string(), Value::String("secret123".to_string()));
475
476        let result = rules.validate_request(&data).await;
477        assert!(result.is_ok());
478
479        // Invalid non-matching passwords
480        let mut data = HashMap::new();
481        data.insert("password".to_string(), Value::String("secret123".to_string()));
482        data.insert("password_confirmation".to_string(), Value::String("different".to_string()));
483
484        let result = rules.validate_request(&data).await;
485        assert!(result.is_err());
486    }
487
488    #[tokio::test]
489    async fn test_rules_builder_required_string() {
490        let rules = RulesBuilder::new()
491            .required_string("name", Some(2), Some(50))
492            .build();
493
494        // Valid string
495        let result = rules.validate_field("name", &Value::String("John".to_string())).await;
496        assert!(result.is_ok());
497
498        // Empty string (should fail required)
499        let result = rules.validate_field("name", &Value::String("".to_string())).await;
500        assert!(result.is_err());
501
502        // Too short string
503        let result = rules.validate_field("name", &Value::String("J".to_string())).await;
504        assert!(result.is_err());
505    }
506
507    #[tokio::test]
508    async fn test_rules_builder_required_email() {
509        let rules = RulesBuilder::new()
510            .required_email("email")
511            .build();
512
513        // Valid email
514        let result = rules.validate_field("email", &Value::String("test@example.com".to_string())).await;
515        assert!(result.is_ok());
516
517        // Empty email (should fail required)
518        let result = rules.validate_field("email", &Value::String("".to_string())).await;
519        assert!(result.is_err());
520
521        // Invalid email format
522        let result = rules.validate_field("email", &Value::String("not-an-email".to_string())).await;
523        assert!(result.is_err());
524    }
525
526    #[tokio::test]
527    async fn test_rules_builder_optional_email() {
528        let rules = RulesBuilder::new()
529            .optional_email("email")
530            .build();
531
532        // Valid email
533        let result = rules.validate_field("email", &Value::String("test@example.com".to_string())).await;
534        assert!(result.is_ok());
535
536        // Null email (should pass - it's optional)
537        let result = rules.validate_field("email", &Value::Null).await;
538        assert!(result.is_ok());
539
540        // Invalid email format (should still fail)
541        let result = rules.validate_field("email", &Value::String("not-an-email".to_string())).await;
542        assert!(result.is_err());
543    }
544
545    #[tokio::test]
546    async fn test_rules_builder_required_number() {
547        let rules = RulesBuilder::new()
548            .required_number("age", Some(0.0), Some(120.0))
549            .build();
550
551        // Valid number
552        let result = rules.validate_field("age", &Value::Number(serde_json::Number::from(25))).await;
553        assert!(result.is_ok());
554
555        // Null (should fail required)
556        let result = rules.validate_field("age", &Value::Null).await;
557        assert!(result.is_err());
558
559        // Out of range
560        let result = rules.validate_field("age", &Value::Number(serde_json::Number::from(150))).await;
561        assert!(result.is_err());
562    }
563
564    #[tokio::test]
565    async fn test_rules_builder_required_integer() {
566        let rules = RulesBuilder::new()
567            .required_integer("count", Some(1.0), Some(100.0))
568            .build();
569
570        // Valid integer
571        let result = rules.validate_field("count", &Value::Number(serde_json::Number::from(10))).await;
572        assert!(result.is_ok());
573
574        // Decimal (should fail integer check)
575        let result = rules.validate_field("count", &Value::Number(serde_json::Number::from_f64(10.5).unwrap())).await;
576        assert!(result.is_err());
577    }
578
579    #[tokio::test]
580    async fn test_rules_builder_pattern() {
581        let rules = RulesBuilder::new()
582            .pattern("code", r"^[A-Z]{3}$")
583            .build();
584
585        // Valid pattern
586        let result = rules.validate_field("code", &Value::String("ABC".to_string())).await;
587        assert!(result.is_ok());
588
589        // Invalid pattern
590        let result = rules.validate_field("code", &Value::String("abc".to_string())).await;
591        assert!(result.is_err());
592    }
593
594    #[tokio::test]
595    async fn test_rules_builder_one_of() {
596        let rules = RulesBuilder::new()
597            .one_of("status", vec!["active".to_string(), "inactive".to_string()])
598            .build();
599
600        // Valid value
601        let result = rules.validate_field("status", &Value::String("active".to_string())).await;
602        assert!(result.is_ok());
603
604        // Invalid value
605        let result = rules.validate_field("status", &Value::String("unknown".to_string())).await;
606        assert!(result.is_err());
607    }
608
609    #[tokio::test]
610    async fn test_rules_combined_field_and_request_validation() {
611        let password_match_rule = CustomValidator::new("password_match", |value, _field| {
612            if let Some(obj) = value.as_object() {
613                let password = obj.get("password").and_then(|v| v.as_str());
614                let confirmation = obj.get("password_confirmation").and_then(|v| v.as_str());
615                
616                if let (Some(pwd), Some(conf)) = (password, confirmation) {
617                    if pwd == conf {
618                        Ok(())
619                    } else {
620                        Err(ValidationError::new("password_confirmation", "Passwords do not match").into())
621                    }
622                } else {
623                    Ok(())
624                }
625            } else {
626                Ok(())
627            }
628        });
629
630        let rules = RulesBuilder::new()
631            .required_string("password", Some(8), None)
632            .required_string("password_confirmation", Some(8), None)
633            .request_rule(password_match_rule)
634            .build();
635
636        let mut data = HashMap::new();
637        data.insert("password".to_string(), Value::String("password123".to_string()));
638        data.insert("password_confirmation".to_string(), Value::String("password123".to_string()));
639
640        // Should validate both fields individually and the request as a whole
641        let result = rules.validate(&data).await;
642        assert!(result.is_ok());
643
644        // Test with non-matching passwords
645        let mut data = HashMap::new();
646        data.insert("password".to_string(), Value::String("password123".to_string()));
647        data.insert("password_confirmation".to_string(), Value::String("different".to_string()));
648
649        let result = rules.validate(&data).await;
650        assert!(result.is_err());
651        
652        let errors = result.unwrap_err();
653        assert!(errors.has_field_errors("password_confirmation"));
654    }
655}