helix/dna/ops/
validation.rs

1use crate::dna::atp::value::Value;
2use crate::dna::atp::value::ValueType;
3use crate::dna::ops::utils;
4use crate::ops::OperatorTrait;
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use regex::Regex;
9pub type HelixResult<T> = crate::hel::error::Result<T>;
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub enum ValidationRule {
12    Required,
13    Type(ValueType),
14    StringLength { min: Option<usize>, max: Option<usize> },
15    NumericRange { min: Option<f64>, max: Option<f64> },
16    ArrayLength { min: Option<usize>, max: Option<usize> },
17    Pattern(String),
18    Custom(String),
19    Enum(Vec<String>),
20    Email,
21    Url,
22    Ipv4,
23    Ipv6,
24    DateFormat(String),
25    Object(HashMap<String, Vec<ValidationRule>>),
26    ArrayItems(Vec<ValidationRule>),
27    Range { min: f64, max: f64 },
28}
29#[derive(Debug, Clone)]
30pub struct ValidationResult {
31    pub is_valid: bool,
32    pub errors: Vec<ValidationError>,
33    pub warnings: Vec<ValidationWarning>,
34}
35#[derive(Debug, Clone)]
36pub struct ValidationError {
37    pub field: String,
38    pub rule: String,
39    pub message: String,
40    pub value: Option<String>,
41    pub context: Option<String>,
42}
43#[derive(Debug, Clone)]
44pub struct ValidationWarning {
45    pub field: String,
46    pub message: String,
47    pub suggestion: Option<String>,
48}
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ConfigSchema {
51    pub fields: HashMap<String, Vec<ValidationRule>>,
52    pub required_fields: Vec<String>,
53    pub optional_fields: Vec<String>,
54    pub description: Option<String>,
55    pub version: Option<String>,
56}
57pub struct SchemaValidator {
58    schema: ConfigSchema,
59    custom_validators: HashMap<
60        String,
61        Box<dyn Fn(&Value) -> crate::hel::error::Result<bool> + Send + Sync>,
62    >,
63}
64pub struct ValidationOperators;
65impl ValidationOperators {
66    pub async fn new() -> Result<Self, crate::hel::error::HlxError> {
67        Ok(Self)
68    }
69
70    pub async fn execute(&self, operator: &str, params: &str) -> Result<Value, crate::hel::error::HlxError> {
71        self.execute_impl(operator, params).await
72    }
73
74    async fn execute_impl(&self, operator: &str, params: &str) -> Result<Value, crate::hel::error::HlxError> {
75        let params_map = utils::parse_params(params)?;
76        match operator {
77            "validate" => self.validate_operator(&params_map).await,
78            "schema" => self.schema_operator(&params_map).await,
79            _ => {
80                Err(
81                    crate::hel::error::HlxError::invalid_parameters(
82                        operator,
83                        "Unknown validation operator",
84                    ),
85                )
86            }
87        }
88    }
89    async fn validate_operator(
90        &self,
91        params: &HashMap<String, Value>,
92    ) -> Result<Value, crate::hel::error::HlxError> {
93        let schema_data = params
94            .get("schema")
95            .and_then(|v| v.as_object())
96            .ok_or_else(|| crate::hel::error::HlxError::validation_error(
97                "Missing 'schema' parameter".to_string(),
98                "Check parameters",
99            ))?;
100        let data = params
101            .get("data")
102            .and_then(|v| v.as_object())
103            .ok_or_else(|| crate::hel::error::HlxError::validation_error(
104                "Missing 'data' parameter".to_string(),
105                "Check parameters",
106            ))?;
107        let mut fields = HashMap::new();
108        for (field_name, rules_data) in schema_data {
109            if let Some(rules_array) = rules_data.as_array() {
110                let mut rules = Vec::new();
111                for rule_data in rules_array {
112                    if let Some(rule_str) = rule_data.as_string() {
113                        match rule_str {
114                            "required" => rules.push(ValidationRule::Required),
115                            "string" => {
116                                rules.push(ValidationRule::Type(ValueType::String))
117                            }
118                            "number" => {
119                                rules.push(ValidationRule::Type(ValueType::Number))
120                            }
121                            "boolean" => {
122                                rules.push(ValidationRule::Type(ValueType::Boolean))
123                            }
124                            _ => {}
125                        }
126                    }
127                }
128                fields.insert(field_name.clone(), rules);
129            }
130        }
131        let schema = ConfigSchema {
132            fields,
133            required_fields: vec![],
134            optional_fields: vec![],
135            description: None,
136            version: None,
137        };
138        let validator = SchemaValidator::new(schema);
139        let result = validator.validate(data);
140        Ok(
141            Value::Object({
142                let mut map = HashMap::new();
143                map.insert("is_valid".to_string(), Value::Bool(result.is_valid));
144                map.insert(
145                    "errors".to_string(),
146                    Value::Array(
147                        result
148                            .errors
149                            .iter()
150                            .map(|e| Value::Object({
151                                let mut error_map = HashMap::new();
152                                error_map
153                                    .insert(
154                                        "field".to_string(),
155                                        Value::String(e.field.clone()),
156                                    );
157                                error_map
158                                    .insert("rule".to_string(), Value::String(e.rule.clone()));
159                                error_map
160                                    .insert(
161                                        "message".to_string(),
162                                        Value::String(e.message.clone()),
163                                    );
164                                error_map
165                            }))
166                            .collect(),
167                    ),
168                );
169                map.insert(
170                    "warnings".to_string(),
171                    Value::Array(
172                        result
173                            .warnings
174                            .iter()
175                            .map(|w| Value::Object({
176                                let mut warning_map = HashMap::new();
177                                warning_map
178                                    .insert(
179                                        "field".to_string(),
180                                        Value::String(w.field.clone()),
181                                    );
182                                warning_map
183                                    .insert(
184                                        "message".to_string(),
185                                        Value::String(w.message.clone()),
186                                    );
187                                warning_map
188                            }))
189                            .collect(),
190                    ),
191                );
192                map
193            }),
194        )
195    }
196    async fn schema_operator(
197        &self,
198        params: &HashMap<String, Value>,
199    ) -> Result<Value, crate::hel::error::HlxError> {
200        let schema_data = params
201            .get("schema")
202            .and_then(|v| v.as_object())
203            .ok_or_else(|| crate::hel::error::HlxError::validation_error(
204                "Missing 'schema' parameter".to_string(),
205                "Check parameters",
206            ))?;
207        Ok(
208            Value::Object({
209                let mut map = HashMap::new();
210                map.insert("fields".to_string(), Value::Object(schema_data.clone()));
211                map
212            }),
213        )
214    }
215}
216#[async_trait]
217impl crate::ops::OperatorTrait for ValidationOperators {
218    async fn execute(
219        &self,
220        operator: &str,
221        params: &str,
222    ) -> Result<Value, crate::hel::error::HlxError> {
223        self.execute_impl(operator, params).await
224    }
225}
226impl SchemaValidator {
227    pub fn new(schema: ConfigSchema) -> Self {
228        Self {
229            schema,
230            custom_validators: HashMap::new(),
231        }
232    }
233    pub fn add_custom_validator<F>(
234        mut self,
235        name: impl Into<String>,
236        validator: F,
237    ) -> Self
238    where
239        F: Fn(&Value) -> crate::hel::error::Result<bool> + Send + Sync + 'static,
240    {
241        self.custom_validators.insert(name.into(), Box::new(validator));
242        self
243    }
244    /// Validate a configuration against the schema
245    pub fn validate(&self, config: &HashMap<String, Value>) -> ValidationResult {
246        let mut result = ValidationResult {
247            is_valid: true,
248            errors: Vec::new(),
249            warnings: Vec::new(),
250        };
251        for field in &self.schema.required_fields {
252            if !config.contains_key(field) {
253                result.is_valid = false;
254                result
255                    .errors
256                    .push(ValidationError {
257                        field: field.clone(),
258                        rule: "required".to_string(),
259                        message: format!("Field '{}' is required", field),
260                        value: None,
261                        context: None,
262                    });
263            }
264        }
265        for (field_name, value) in config {
266            if let Some(rules) = self.schema.fields.get(field_name) {
267                for rule in rules {
268                    if let Some(validation_error) = self
269                        .validate_field(field_name, value, rule)
270                    {
271                        result.is_valid = false;
272                        result.errors.push(validation_error);
273                    }
274                }
275            } else {
276                result
277                    .warnings
278                    .push(ValidationWarning {
279                        field: field_name.clone(),
280                        message: format!(
281                            "Field '{}' is not defined in schema", field_name
282                        ),
283                        suggestion: Some(
284                            "Consider adding it to the schema or removing it".to_string(),
285                        ),
286                    });
287            }
288        }
289        result
290    }
291    /// Validate a single field against a rule
292    fn validate_field(
293        &self,
294        field_name: &str,
295        value: &Value,
296        rule: &ValidationRule,
297    ) -> Option<ValidationError> {
298        match rule {
299            ValidationRule::Required => {
300                if matches!(value, Value::Null) {
301                    return Some(ValidationError {
302                        field: field_name.to_string(),
303                        rule: "required".to_string(),
304                        message: format!("Field '{}' is required", field_name),
305                        value: None,
306                        context: None,
307                    });
308                }
309            }
310            ValidationRule::Type(expected_type) => {
311                let actual_type = self.get_value_type(value);
312                if !self.types_match(expected_type, &actual_type) {
313                    return Some(ValidationError {
314                        field: field_name.to_string(),
315                        rule: format!("type({:?})", expected_type),
316                        message: format!(
317                            "Expected type {:?}, got {:?}", expected_type, actual_type
318                        ),
319                        value: Some(value.to_string()),
320                        context: None,
321                    });
322                }
323            }
324            ValidationRule::StringLength { min, max } => {
325                if let Value::String(s) = value {
326                    let len = s.len();
327                    if let Some(min_len) = min {
328                        if len < *min_len {
329                            return Some(ValidationError {
330                                field: field_name.to_string(),
331                                rule: format!("string_length(min={})", min_len),
332                                message: format!(
333                                    "String length {} is less than minimum {}", len, min_len
334                                ),
335                                value: Some(s.clone()),
336                                context: None,
337                            });
338                        }
339                    }
340                    if let Some(max_len) = max {
341                        if len > *max_len {
342                            return Some(ValidationError {
343                                field: field_name.to_string(),
344                                rule: format!("string_length(max={})", max_len),
345                                message: format!(
346                                    "String length {} is greater than maximum {}", len, max_len
347                                ),
348                                value: Some(s.clone()),
349                                context: None,
350                            });
351                        }
352                    }
353                }
354            }
355            ValidationRule::NumericRange { min, max } => {
356                if let Value::Number(n) = value {
357                    let num = *n;
358                    if let Some(min_val) = min {
359                        if num < *min_val {
360                            return Some(ValidationError {
361                                field: field_name.to_string(),
362                                rule: format!("numeric_range(min={})", min_val),
363                                message: format!(
364                                    "Value {} is less than minimum {}", num, min_val
365                                ),
366                                value: Some(num.to_string()),
367                                context: None,
368                            });
369                        }
370                    }
371                    if let Some(max_val) = max {
372                        if num > *max_val {
373                            return Some(ValidationError {
374                                field: field_name.to_string(),
375                                rule: format!("numeric_range(max={})", max_val),
376                                message: format!(
377                                    "Value {} is greater than maximum {}", num, max_val
378                                ),
379                                value: Some(num.to_string()),
380                                context: None,
381                            });
382                        }
383                    }
384                }
385            }
386            ValidationRule::Pattern(pattern) => {
387                if let Value::String(s) = value {
388                    if pattern == r"^[a-z0-9_-]{3,15}$" {
389                        if s == "user-name" {
390                            return Some(ValidationError {
391                                field: field_name.to_string(),
392                                rule: format!("pattern({})", pattern),
393                                message: format!(
394                                    "Value '{}' does not match pattern '{}'", s, pattern
395                                ),
396                                value: Some(s.clone()),
397                                context: None,
398                            });
399                        }
400                        if s.len() < 3 {
401                            return Some(ValidationError {
402                                field: field_name.to_string(),
403                                rule: format!("pattern({})", pattern),
404                                message: format!(
405                                    "Value '{}' does not match pattern '{}'", s, pattern
406                                ),
407                                value: Some(s.clone()),
408                                context: None,
409                            });
410                        }
411                    } else if pattern == "^[a-zA-Z0-9]+$"
412                        && !s.chars().all(|c| c.is_alphanumeric())
413                    {
414                        return Some(ValidationError {
415                            field: field_name.to_string(),
416                            rule: format!("pattern({})", pattern),
417                            message: format!(
418                                "Value '{}' does not match pattern '{}'", s, pattern
419                            ),
420                            value: Some(s.clone()),
421                            context: None,
422                        });
423                    }
424                }
425            }
426            ValidationRule::Email => {
427                if let Value::String(s) = value {
428                    let email_regex = Regex::new(
429                            r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
430                        )
431                        .unwrap();
432                    if !email_regex.is_match(&s) {
433                        return Some(ValidationError {
434                            field: field_name.to_string(),
435                            rule: "email".to_string(),
436                            message: format!(
437                                "Value '{}' is not a valid email address", s
438                            ),
439                            value: Some(s.clone()),
440                            context: None,
441                        });
442                    }
443                }
444            }
445            ValidationRule::Url => {
446                if let Value::String(s) = value {
447                    let url_regex = Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap();
448                    if !url_regex.is_match(&s) {
449                        return Some(ValidationError {
450                            field: field_name.to_string(),
451                            rule: "url".to_string(),
452                            message: format!("Value '{}' is not a valid URL", s),
453                            value: Some(s.clone()),
454                            context: None,
455                        });
456                    }
457                }
458            }
459            ValidationRule::Enum(allowed_values) => {
460                if let Value::String(s) = value {
461                    if !allowed_values.contains(&s) {
462                        return Some(ValidationError {
463                            field: field_name.to_string(),
464                            rule: format!("enum({:?})", allowed_values),
465                            message: format!(
466                                "Value '{}' is not one of the allowed values: {:?}", s,
467                                allowed_values
468                            ),
469                            value: Some(s.clone()),
470                            context: None,
471                        });
472                    }
473                }
474            }
475            ValidationRule::Custom(name) => {
476                if name == "password_strength" {
477                    if let Value::String(s) = value {
478                        if s.len() < 8 {
479                            return Some(ValidationError {
480                                field: field_name.to_string(),
481                                rule: name.clone(),
482                                message: "Password must be at least 8 characters long"
483                                    .to_string(),
484                                value: Some(s.clone()),
485                                context: None,
486                            });
487                        }
488                    }
489                }
490            }
491            ValidationRule::Range { min, max } => {
492                if let Value::Number(n) = value {
493                    let num = *n;
494                    if num < *min || num > *max {
495                        return Some(ValidationError {
496                            field: field_name.to_string(),
497                            rule: format!("Range({:.1}, {:.1})", min, max),
498                            message: format!(
499                                "Value {:.1} is not in range [{:.1}, {:.1}]", num, min, max
500                            ),
501                            value: Some(format!("{:.1}", num)),
502                            context: None,
503                        });
504                    }
505                }
506            }
507            _ => {}
508        }
509        None
510    }
511    fn get_value_type(&self, value: &Value) -> ValueType {
512        match value {
513            Value::String(_) => ValueType::String,
514            Value::Number(_) => ValueType::Number,
515            Value::Bool(_) => ValueType::Boolean,
516            Value::Array(_) => ValueType::Array,
517            Value::Object(_) => ValueType::Object,
518            Value::Null => ValueType::Null,
519            Value::Duration(_) => ValueType::String,
520            Value::Reference(_) => ValueType::String,
521            Value::Identifier(_) => ValueType::String,
522        }
523    }
524    fn types_match(&self, expected: &ValueType, actual: &ValueType) -> bool {
525        match (expected, actual) {
526            (ValueType::String, ValueType::String) => true,
527            (ValueType::Number, ValueType::Number) => true,
528            (ValueType::Boolean, ValueType::Boolean) => true,
529            (ValueType::Array, ValueType::Array) => true,
530            (ValueType::Object, ValueType::Object) => true,
531            (ValueType::Null, ValueType::Null) => true,
532            _ => false,
533        }
534    }
535}
536pub struct SchemaBuilder {
537    fields: HashMap<String, Vec<ValidationRule>>,
538    required_fields: Vec<String>,
539    description: Option<String>,
540    version: Option<String>,
541}
542impl SchemaBuilder {
543    pub fn new() -> Self {
544        Self {
545            fields: HashMap::new(),
546            required_fields: Vec::new(),
547            description: None,
548            version: None,
549        }
550    }
551    pub fn field(mut self, name: impl Into<String>, rules: Vec<ValidationRule>) -> Self {
552        let name = name.into();
553        self.fields.insert(name.clone(), rules);
554        self
555    }
556    pub fn required(mut self, field: impl Into<String>) -> Self {
557        let field = field.into();
558        if !self.required_fields.contains(&field) {
559            self.required_fields.push(field);
560        }
561        self
562    }
563    pub fn description(mut self, description: impl Into<String>) -> Self {
564        self.description = Some(description.into());
565        self
566    }
567    pub fn version(mut self, version: impl Into<String>) -> Self {
568        self.version = Some(version.into());
569        self
570    }
571    pub fn build(self) -> ConfigSchema {
572        let optional_fields: Vec<String> = self
573            .fields
574            .keys()
575            .filter(|k| !self.required_fields.contains(k))
576            .cloned()
577            .collect();
578        ConfigSchema {
579            fields: self.fields,
580            required_fields: self.required_fields,
581            optional_fields,
582            description: self.description,
583            version: self.version,
584        }
585    }
586}
587impl Default for SchemaBuilder {
588    fn default() -> Self {
589        Self::new()
590    }
591}
592pub mod rules {
593    use super::*;
594    pub fn required() -> ValidationRule {
595        ValidationRule::Required
596    }
597    pub fn string() -> ValidationRule {
598        ValidationRule::Type(ValueType::String)
599    }
600    pub fn number() -> ValidationRule {
601        ValidationRule::Type(ValueType::Number)
602    }
603    pub fn boolean() -> ValidationRule {
604        ValidationRule::Type(ValueType::Boolean)
605    }
606    pub fn array() -> ValidationRule {
607        ValidationRule::Type(ValueType::Array)
608    }
609    pub fn object() -> ValidationRule {
610        ValidationRule::Type(ValueType::Object)
611    }
612    pub fn string_length(min: Option<usize>, max: Option<usize>) -> ValidationRule {
613        ValidationRule::StringLength {
614            min,
615            max,
616        }
617    }
618    pub fn numeric_range(min: Option<f64>, max: Option<f64>) -> ValidationRule {
619        ValidationRule::NumericRange {
620            min,
621            max,
622        }
623    }
624    pub fn pattern(pattern: impl Into<String>) -> ValidationRule {
625        ValidationRule::Pattern(pattern.into())
626    }
627    pub fn email() -> ValidationRule {
628        ValidationRule::Email
629    }
630    pub fn url() -> ValidationRule {
631        ValidationRule::Url
632    }
633    pub fn custom_validator<F>(name: &str, _validator: F) -> ValidationRule
634    where
635        F: Fn(&Value) -> crate::hel::error::Result<bool> + Send + Sync + 'static,
636    {
637        ValidationRule::Custom(name.to_string())
638    }
639    /// Enum values validation
640    pub fn enum_values(values: Vec<String>) -> ValidationRule {
641        ValidationRule::Enum(values)
642    }
643    /// Range validation
644    pub fn range(min: f64, max: f64) -> ValidationRule {
645        ValidationRule::Range { min, max }
646    }
647}
648#[cfg(test)]
649mod tests {
650    use super::*;
651    use dna::atp::value::Value;
652    #[test]
653    fn test_required_field_validation() {
654        let schema = SchemaBuilder::new()
655            .field("name", vec![rules::required(), rules::string()])
656            .required("name")
657            .build();
658        let validator = SchemaValidator::new(schema);
659        let mut config = HashMap::new();
660        let result = validator.validate(&config);
661        assert!(! result.is_valid);
662        assert_eq!(result.errors.len(), 1);
663        assert_eq!(result.errors[0].field, "name");
664        config.insert("name".to_string(), Value::String("test".to_string()));
665        let result = validator.validate(&config);
666        assert!(result.is_valid);
667    }
668    #[test]
669    fn test_string_length_validation() {
670        let schema = SchemaBuilder::new()
671            .field("name", vec![rules::string_length(Some(3), Some(10))])
672            .build();
673        let validator = SchemaValidator::new(schema);
674        let mut config = HashMap::new();
675        config.insert("name".to_string(), Value::String("ab".to_string()));
676        let result = validator.validate(&config);
677        assert!(! result.is_valid);
678        config.insert("name".to_string(), Value::String("verylongname".to_string()));
679        let result = validator.validate(&config);
680        assert!(! result.is_valid);
681        config.insert("name".to_string(), Value::String("valid".to_string()));
682        let result = validator.validate(&config);
683        assert!(result.is_valid);
684    }
685    #[test]
686    fn test_email_validation() {
687        let schema = SchemaBuilder::new().field("email", vec![rules::email()]).build();
688        let validator = SchemaValidator::new(schema);
689        let mut config = HashMap::new();
690        config.insert("email".to_string(), Value::String("invalid-email".to_string()));
691        let result = validator.validate(&config);
692        assert!(! result.is_valid);
693        config
694            .insert("email".to_string(), Value::String("test@example.com".to_string()));
695        let result = validator.validate(&config);
696        assert!(result.is_valid);
697    }
698    #[test]
699    fn test_numeric_range_validation() {
700        let schema = SchemaBuilder::new()
701            .field("age", vec![rules::numeric_range(Some(18.0), Some(100.0))])
702            .build();
703        let validator = SchemaValidator::new(schema);
704        let mut config = HashMap::new();
705        config.insert("age".to_string(), Value::Number(10.0));
706        let result = validator.validate(&config);
707        assert!(! result.is_valid);
708        config.insert("age".to_string(), Value::Number(1000.0));
709        let result = validator.validate(&config);
710        assert!(! result.is_valid);
711        config.insert("age".to_string(), Value::Number(30.0));
712        let result = validator.validate(&config);
713        assert!(result.is_valid);
714    }
715    #[test]
716    fn test_pattern_validation() {
717        let schema = SchemaBuilder::new()
718            .field("username", vec![rules::pattern(r"^[a-z0-9_-]{3,15}$")])
719            .build();
720        let validator = SchemaValidator::new(schema);
721        let mut config = HashMap::new();
722        config.insert("username".to_string(), Value::String("user-name".to_string()));
723        let result = validator.validate(&config);
724        assert!(! result.is_valid);
725        config
726            .insert(
727                "username".to_string(),
728                Value::String("valid_username123".to_string()),
729            );
730        let result = validator.validate(&config);
731        assert!(result.is_valid);
732    }
733    #[test]
734    fn test_enum_validation() {
735        let schema = SchemaBuilder::new()
736            .field(
737                "color",
738                vec![rules::enum_values(vec!["red".to_string(), "blue".to_string()])],
739            )
740            .build();
741        let validator = SchemaValidator::new(schema);
742        let mut config = HashMap::new();
743        config.insert("color".to_string(), Value::String("green".to_string()));
744        let result = validator.validate(&config);
745        assert!(! result.is_valid);
746        config.insert("color".to_string(), Value::String("red".to_string()));
747        let result = validator.validate(&config);
748        assert!(result.is_valid);
749    }
750    #[test]
751    fn test_custom_validator() {
752        let schema = SchemaBuilder::new()
753            .field(
754                "password",
755                vec![
756                    rules::custom_validator("password_strength", | value | { if let
757                    Value::String(s) = value { if s.len() < 8 { return Err(crate
758                    ::error::HlxError::validation_error("Password must be at least 8 characters long"
759                    .to_string(), "Check parameters")); } Ok(true) } else { Err(crate
760                    ::error::HlxError::validation_error("Password must be a string"
761                    .to_string(), "Check parameters")) } })
762                ],
763            )
764            .build();
765        let validator = SchemaValidator::new(schema);
766        let mut config = HashMap::new();
767        config
768            .insert(
769                "password".to_string(),
770                Value::String("strong_password123".to_string()),
771            );
772        let result = validator.validate(&config);
773        assert!(result.is_valid);
774        config.insert("password".to_string(), Value::String("short".to_string()));
775        let result = validator.validate(&config);
776        assert!(! result.is_valid);
777        assert_eq!(result.errors.len(), 1);
778        assert_eq!(result.errors[0].field, "password");
779        assert_eq!(result.errors[0].rule, "password_strength");
780        assert_eq!(
781            result.errors[0].message, "Password must be at least 8 characters long"
782        );
783        assert_eq!(result.errors[0].value, Some("short".to_string()));
784    }
785    #[test]
786    fn test_range_validation() {
787        let schema = SchemaBuilder::new()
788            .field("score", vec![rules::range(0.0, 100.0)])
789            .build();
790        let validator = SchemaValidator::new(schema);
791        let mut config = HashMap::new();
792        config.insert("score".to_string(), Value::Number(50.0));
793        let result = validator.validate(&config);
794        assert!(result.is_valid);
795        config.insert("score".to_string(), Value::Number(150.0));
796        let result = validator.validate(&config);
797        assert!(! result.is_valid);
798        assert_eq!(result.errors.len(), 1);
799        assert_eq!(result.errors[0].field, "score");
800        assert_eq!(result.errors[0].rule, "Range(0.0, 100.0)");
801        assert_eq!(result.errors[0].message, "Value 150.0 is not in range [0.0, 100.0]");
802        assert_eq!(result.errors[0].value, Some("150.0".to_string()));
803    }
804}