ddex_core/models/
validation.rs

1//! # Comprehensive Attribute Validation System
2//! 
3//! This module provides validation for XML attributes including:
4//! - Required attribute validation
5//! - Format validation (URIs, dates, enums, etc.)
6//! - Custom attribute policy enforcement
7//! - DDEX-specific validation rules
8//! - Cross-attribute validation
9//! - Namespace-aware validation
10
11use crate::models::{AttributeMap, AttributeValue, QName, AttributeType};
12use indexmap::{IndexMap, IndexSet};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use thiserror::Error;
16use chrono::{DateTime, Utc, NaiveDate};
17use url::Url;
18use regex::Regex;
19
20/// Comprehensive validation errors for attributes
21#[derive(Debug, Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub enum AttributeValidationError {
23    #[error("Missing required attribute: {attribute}")]
24    MissingRequired { attribute: QName },
25    
26    #[error("Invalid format for attribute {attribute}: {reason}")]
27    InvalidFormat { attribute: QName, reason: String },
28    
29    #[error("Invalid enum value for attribute {attribute}: '{value}', expected one of: {allowed_values:?}")]
30    InvalidEnumValue { 
31        attribute: QName, 
32        value: String, 
33        allowed_values: Vec<String> 
34    },
35    
36    #[error("Value out of range for attribute {attribute}: {value}, expected {range}")]
37    ValueOutOfRange { 
38        attribute: QName, 
39        value: String, 
40        range: String 
41    },
42    
43    #[error("Invalid type for attribute {attribute}: expected {expected}, got {actual}")]
44    InvalidType { 
45        attribute: QName, 
46        expected: AttributeType, 
47        actual: AttributeType 
48    },
49    
50    #[error("Custom validation failed for attribute {attribute}: {rule}")]
51    CustomValidationFailed { 
52        attribute: QName, 
53        rule: String 
54    },
55    
56    #[error("Cross-attribute validation failed: {message}")]
57    CrossAttributeValidationFailed { message: String },
58    
59    #[error("Namespace validation failed for attribute {attribute}: {reason}")]
60    NamespaceValidationFailed { 
61        attribute: QName, 
62        reason: String 
63    },
64    
65    #[error("Policy violation for attribute {attribute}: {policy}")]
66    PolicyViolation { 
67        attribute: QName, 
68        policy: String 
69    },
70}
71
72/// Validation result containing errors and warnings
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct ValidationResult {
75    pub errors: Vec<AttributeValidationError>,
76    pub warnings: Vec<String>,
77    pub is_valid: bool,
78}
79
80impl ValidationResult {
81    pub fn new() -> Self {
82        Self {
83            errors: Vec::new(),
84            warnings: Vec::new(),
85            is_valid: true,
86        }
87    }
88    
89    pub fn add_error(&mut self, error: AttributeValidationError) {
90        self.errors.push(error);
91        self.is_valid = false;
92    }
93    
94    pub fn add_warning(&mut self, warning: String) {
95        self.warnings.push(warning);
96    }
97    
98    pub fn merge(&mut self, other: ValidationResult) {
99        self.errors.extend(other.errors);
100        self.warnings.extend(other.warnings);
101        if !other.is_valid {
102            self.is_valid = false;
103        }
104    }
105}
106
107impl Default for ValidationResult {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113/// Attribute validation rule definition
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115pub enum ValidationRule {
116    /// Attribute is required
117    Required,
118    /// Must match specific type
119    Type(AttributeType),
120    /// Must be one of specified enum values
121    Enum(Vec<String>),
122    /// Must match regular expression
123    Regex(String),
124    /// Must be valid URI
125    Uri,
126    /// Must be valid email address
127    Email,
128    /// Must be valid ISO date (YYYY-MM-DD)
129    Date,
130    /// Must be valid ISO datetime
131    DateTime,
132    /// Numeric range validation (using i64 for Eq compliance)
133    Range { min: Option<i64>, max: Option<i64> },
134    /// String length validation
135    Length { min: Option<usize>, max: Option<usize> },
136    /// Custom validation function (by name)
137    Custom(String),
138    /// Cross-attribute dependency
139    Dependency { 
140        depends_on: QName, 
141        condition: DependencyCondition 
142    },
143}
144
145/// Dependency condition for cross-attribute validation
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub enum DependencyCondition {
148    /// Required when dependent attribute exists
149    RequiredWhenExists,
150    /// Required when dependent attribute has specific value
151    RequiredWhenEquals(String),
152    /// Forbidden when dependent attribute exists
153    ForbiddenWhenExists,
154    /// Must have same value as dependent attribute
155    MustMatch,
156    /// Must have different value from dependent attribute
157    MustDiffer,
158}
159
160/// Validation policy for controlling attribute validation behavior
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
162pub struct ValidationPolicy {
163    /// Whether to allow unknown attributes
164    pub allow_unknown_attributes: bool,
165    /// Whether to treat warnings as errors
166    pub strict_mode: bool,
167    /// Whether to validate namespace declarations
168    pub validate_namespaces: bool,
169    /// Custom policies by namespace
170    pub namespace_policies: IndexMap<String, NamespacePolicy>,
171    /// Element-specific validation overrides
172    pub element_overrides: IndexMap<QName, ElementValidationConfig>,
173}
174
175impl Default for ValidationPolicy {
176    fn default() -> Self {
177        Self {
178            allow_unknown_attributes: true,
179            strict_mode: false,
180            validate_namespaces: true,
181            namespace_policies: IndexMap::new(),
182            element_overrides: IndexMap::new(),
183        }
184    }
185}
186
187/// Namespace-specific validation policy
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189pub struct NamespacePolicy {
190    /// Whether attributes in this namespace are allowed
191    pub allowed: bool,
192    /// Whether to validate format for this namespace
193    pub validate_format: bool,
194    /// Custom validation rules for this namespace
195    pub custom_rules: IndexMap<String, Vec<ValidationRule>>,
196}
197
198/// Element-specific validation configuration
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
200pub struct ElementValidationConfig {
201    /// Required attributes for this element
202    pub required_attributes: IndexSet<QName>,
203    /// Forbidden attributes for this element
204    pub forbidden_attributes: IndexSet<QName>,
205    /// Custom validation rules
206    pub custom_rules: IndexMap<QName, Vec<ValidationRule>>,
207}
208
209/// Comprehensive attribute validator
210#[derive(Debug, Clone)]
211pub struct AttributeValidator {
212    /// Global validation rules by attribute name
213    global_rules: IndexMap<QName, Vec<ValidationRule>>,
214    /// Element-specific validation rules
215    element_rules: IndexMap<QName, IndexMap<QName, Vec<ValidationRule>>>,
216    /// Validation policy
217    policy: ValidationPolicy,
218    /// Custom validation functions
219    custom_validators: HashMap<String, fn(&AttributeValue) -> Result<(), String>>,
220    /// Compiled regex cache
221    regex_cache: HashMap<String, Regex>,
222}
223
224impl AttributeValidator {
225    pub fn new() -> Self {
226        let mut validator = Self {
227            global_rules: IndexMap::new(),
228            element_rules: IndexMap::new(),
229            policy: ValidationPolicy::default(),
230            custom_validators: HashMap::new(),
231            regex_cache: HashMap::new(),
232        };
233        
234        validator.setup_ddex_rules();
235        validator.setup_xml_schema_rules();
236        validator.setup_custom_validators();
237        validator
238    }
239    
240    /// Create validator with specific policy
241    pub fn with_policy(policy: ValidationPolicy) -> Self {
242        let mut validator = Self::new();
243        validator.policy = policy;
244        validator
245    }
246    
247    /// Add global validation rule for an attribute
248    pub fn add_global_rule(&mut self, attribute: QName, rule: ValidationRule) {
249        self.global_rules
250            .entry(attribute)
251            .or_insert_with(Vec::new)
252            .push(rule);
253    }
254    
255    /// Add element-specific validation rule
256    pub fn add_element_rule(&mut self, element: QName, attribute: QName, rule: ValidationRule) {
257        self.element_rules
258            .entry(element)
259            .or_insert_with(IndexMap::new)
260            .entry(attribute)
261            .or_insert_with(Vec::new)
262            .push(rule);
263    }
264    
265    /// Add custom validation function
266    pub fn add_custom_validator<F>(&mut self, name: String, _validator: F)
267    where
268        F: Fn(&AttributeValue) -> Result<(), String> + 'static,
269    {
270        // For now, we'll store the name and implement specific validators
271        // In a real implementation, we'd need a more complex system for dynamic functions
272        match name.as_str() {
273            "ddex_territory_code" => {
274                self.custom_validators.insert(name, Self::validate_territory_code);
275            },
276            "ddex_language_code" => {
277                self.custom_validators.insert(name, Self::validate_language_code);
278            },
279            "ddex_currency_code" => {
280                self.custom_validators.insert(name, Self::validate_currency_code);
281            },
282            _ => {
283                // Store placeholder - in real implementation would store actual function
284            }
285        }
286    }
287    
288    /// Validate attributes for a specific element
289    pub fn validate_element_attributes(
290        &mut self,
291        element_name: &QName,
292        attributes: &AttributeMap,
293    ) -> ValidationResult {
294        let mut result = ValidationResult::new();
295        
296        // Get applicable rules
297        let element_rules = self.element_rules.get(element_name).cloned().unwrap_or_default();
298        
299        // Check required attributes first
300        self.validate_required_attributes(&element_rules, attributes, &mut result);
301        
302        // Validate each attribute
303        for (attr_qname, attr_value) in attributes {
304            self.validate_single_attribute(
305                element_name,
306                attr_qname,
307                attr_value,
308                &element_rules,
309                &mut result,
310            );
311        }
312        
313        // Cross-attribute validation
314        self.validate_cross_attributes(element_name, attributes, &mut result);
315        
316        // Policy validation
317        self.validate_policy_compliance(element_name, attributes, &mut result);
318        
319        result
320    }
321    
322    /// Validate all attributes globally (no element context)
323    pub fn validate_global_attributes(&mut self, attributes: &AttributeMap) -> ValidationResult {
324        let mut result = ValidationResult::new();
325        
326        for (attr_qname, attr_value) in attributes {
327            if let Some(rules) = self.global_rules.get(attr_qname).cloned() {
328                for rule in &rules {
329                    if let Err(error) = self.apply_validation_rule(attr_qname, attr_value, rule) {
330                        result.add_error(error);
331                    }
332                }
333            }
334        }
335        
336        result
337    }
338    
339    fn validate_required_attributes(
340        &self,
341        element_rules: &IndexMap<QName, Vec<ValidationRule>>,
342        attributes: &AttributeMap,
343        result: &mut ValidationResult,
344    ) {
345        for (attr_qname, rules) in element_rules {
346            if rules.contains(&ValidationRule::Required) && !attributes.contains_key(attr_qname) {
347                result.add_error(AttributeValidationError::MissingRequired {
348                    attribute: attr_qname.clone(),
349                });
350            }
351        }
352    }
353    
354    fn validate_single_attribute(
355        &mut self,
356        _element_name: &QName,
357        attr_qname: &QName,
358        attr_value: &AttributeValue,
359        element_rules: &IndexMap<QName, Vec<ValidationRule>>,
360        result: &mut ValidationResult,
361    ) {
362        // Apply element-specific rules
363        if let Some(rules) = element_rules.get(attr_qname).cloned() {
364            for rule in &rules {
365                if let Err(error) = self.apply_validation_rule(attr_qname, attr_value, rule) {
366                    result.add_error(error);
367                }
368            }
369        }
370        
371        // Apply global rules
372        if let Some(rules) = self.global_rules.get(attr_qname).cloned() {
373            for rule in &rules {
374                if let Err(error) = self.apply_validation_rule(attr_qname, attr_value, rule) {
375                    result.add_error(error);
376                }
377            }
378        }
379        
380        // Namespace validation
381        if self.policy.validate_namespaces {
382            self.validate_namespace_compliance(attr_qname, result);
383        }
384    }
385    
386    fn apply_validation_rule(
387        &mut self,
388        attr_qname: &QName,
389        attr_value: &AttributeValue,
390        rule: &ValidationRule,
391    ) -> Result<(), AttributeValidationError> {
392        match rule {
393            ValidationRule::Required => {
394                // This is handled separately in validate_required_attributes
395                Ok(())
396            },
397            ValidationRule::Type(expected_type) => {
398                let actual_type = self.get_attribute_type(attr_value);
399                if actual_type != *expected_type {
400                    Err(AttributeValidationError::InvalidType {
401                        attribute: attr_qname.clone(),
402                        expected: *expected_type,
403                        actual: actual_type,
404                    })
405                } else {
406                    Ok(())
407                }
408            },
409            ValidationRule::Enum(allowed_values) => {
410                let value_str = attr_value.to_string();
411                if !allowed_values.contains(&value_str) {
412                    Err(AttributeValidationError::InvalidEnumValue {
413                        attribute: attr_qname.clone(),
414                        value: value_str,
415                        allowed_values: allowed_values.clone(),
416                    })
417                } else {
418                    Ok(())
419                }
420            },
421            ValidationRule::Regex(pattern) => {
422                let regex = self.get_or_compile_regex(pattern)?;
423                let value_str = attr_value.to_string();
424                if !regex.is_match(&value_str) {
425                    Err(AttributeValidationError::InvalidFormat {
426                        attribute: attr_qname.clone(),
427                        reason: format!("does not match pattern: {}", pattern),
428                    })
429                } else {
430                    Ok(())
431                }
432            },
433            ValidationRule::Uri => {
434                let value_str = attr_value.to_string();
435                if Url::parse(&value_str).is_err() {
436                    Err(AttributeValidationError::InvalidFormat {
437                        attribute: attr_qname.clone(),
438                        reason: "invalid URI format".to_string(),
439                    })
440                } else {
441                    Ok(())
442                }
443            },
444            ValidationRule::Email => {
445                let value_str = attr_value.to_string();
446                let email_regex = self.get_or_compile_regex(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")?;
447                if !email_regex.is_match(&value_str) {
448                    Err(AttributeValidationError::InvalidFormat {
449                        attribute: attr_qname.clone(),
450                        reason: "invalid email format".to_string(),
451                    })
452                } else {
453                    Ok(())
454                }
455            },
456            ValidationRule::Date => {
457                let value_str = attr_value.to_string();
458                if NaiveDate::parse_from_str(&value_str, "%Y-%m-%d").is_err() {
459                    Err(AttributeValidationError::InvalidFormat {
460                        attribute: attr_qname.clone(),
461                        reason: "invalid date format, expected YYYY-MM-DD".to_string(),
462                    })
463                } else {
464                    Ok(())
465                }
466            },
467            ValidationRule::DateTime => {
468                let value_str = attr_value.to_string();
469                if value_str.parse::<DateTime<Utc>>().is_err() {
470                    Err(AttributeValidationError::InvalidFormat {
471                        attribute: attr_qname.clone(),
472                        reason: "invalid datetime format, expected RFC3339".to_string(),
473                    })
474                } else {
475                    Ok(())
476                }
477            },
478            ValidationRule::Range { min, max } => {
479                self.validate_numeric_range(attr_qname, attr_value, *min, *max)
480            },
481            ValidationRule::Length { min, max } => {
482                self.validate_string_length(attr_qname, attr_value, *min, *max)
483            },
484            ValidationRule::Custom(validator_name) => {
485                if let Some(validator) = self.custom_validators.get(validator_name) {
486                    match validator(attr_value) {
487                        Ok(()) => Ok(()),
488                        Err(reason) => Err(AttributeValidationError::CustomValidationFailed {
489                            attribute: attr_qname.clone(),
490                            rule: reason,
491                        }),
492                    }
493                } else {
494                    Err(AttributeValidationError::CustomValidationFailed {
495                        attribute: attr_qname.clone(),
496                        rule: format!("Unknown validator: {}", validator_name),
497                    })
498                }
499            },
500            ValidationRule::Dependency { depends_on: _, condition: _ } => {
501                // Dependencies are handled in validate_cross_attributes
502                Ok(())
503            },
504        }
505    }
506    
507    fn validate_cross_attributes(
508        &self,
509        _element_name: &QName,
510        attributes: &AttributeMap,
511        result: &mut ValidationResult,
512    ) {
513        // Collect all dependency rules
514        let mut dependencies = Vec::new();
515        
516        for rules in self.global_rules.values() {
517            for rule in rules {
518                if let ValidationRule::Dependency { depends_on, condition } = rule {
519                    dependencies.push((depends_on, condition));
520                }
521            }
522        }
523        
524        // Apply dependency rules
525        for (attr_qname, attr_rules) in &self.global_rules {
526            for rule in attr_rules {
527                if let ValidationRule::Dependency { depends_on, condition } = rule {
528                    if let Err(error) = self.validate_dependency(
529                        attr_qname,
530                        depends_on,
531                        condition,
532                        attributes,
533                    ) {
534                        result.add_error(error);
535                    }
536                }
537            }
538        }
539    }
540    
541    fn validate_dependency(
542        &self,
543        attr_qname: &QName,
544        depends_on: &QName,
545        condition: &DependencyCondition,
546        attributes: &AttributeMap,
547    ) -> Result<(), AttributeValidationError> {
548        let has_attr = attributes.contains_key(attr_qname);
549        let has_dependency = attributes.contains_key(depends_on);
550        
551        match condition {
552            DependencyCondition::RequiredWhenExists => {
553                if has_dependency && !has_attr {
554                    Err(AttributeValidationError::CrossAttributeValidationFailed {
555                        message: format!(
556                            "Attribute {} is required when {} exists",
557                            attr_qname.local_name, depends_on.local_name
558                        ),
559                    })
560                } else {
561                    Ok(())
562                }
563            },
564            DependencyCondition::RequiredWhenEquals(value) => {
565                if let Some(dep_value) = attributes.get(depends_on) {
566                    if dep_value.to_string() == *value && !has_attr {
567                        Err(AttributeValidationError::CrossAttributeValidationFailed {
568                            message: format!(
569                                "Attribute {} is required when {} equals '{}'",
570                                attr_qname.local_name, depends_on.local_name, value
571                            ),
572                        })
573                    } else {
574                        Ok(())
575                    }
576                } else {
577                    Ok(())
578                }
579            },
580            DependencyCondition::ForbiddenWhenExists => {
581                if has_dependency && has_attr {
582                    Err(AttributeValidationError::CrossAttributeValidationFailed {
583                        message: format!(
584                            "Attribute {} is forbidden when {} exists",
585                            attr_qname.local_name, depends_on.local_name
586                        ),
587                    })
588                } else {
589                    Ok(())
590                }
591            },
592            DependencyCondition::MustMatch => {
593                if let (Some(attr_value), Some(dep_value)) = 
594                    (attributes.get(attr_qname), attributes.get(depends_on)) {
595                    if attr_value.to_string() != dep_value.to_string() {
596                        Err(AttributeValidationError::CrossAttributeValidationFailed {
597                            message: format!(
598                                "Attribute {} must match {}",
599                                attr_qname.local_name, depends_on.local_name
600                            ),
601                        })
602                    } else {
603                        Ok(())
604                    }
605                } else {
606                    Ok(())
607                }
608            },
609            DependencyCondition::MustDiffer => {
610                if let (Some(attr_value), Some(dep_value)) = 
611                    (attributes.get(attr_qname), attributes.get(depends_on)) {
612                    if attr_value.to_string() == dep_value.to_string() {
613                        Err(AttributeValidationError::CrossAttributeValidationFailed {
614                            message: format!(
615                                "Attribute {} must differ from {}",
616                                attr_qname.local_name, depends_on.local_name
617                            ),
618                        })
619                    } else {
620                        Ok(())
621                    }
622                } else {
623                    Ok(())
624                }
625            },
626        }
627    }
628    
629    fn validate_policy_compliance(
630        &self,
631        _element_name: &QName,
632        attributes: &AttributeMap,
633        result: &mut ValidationResult,
634    ) {
635        if !self.policy.allow_unknown_attributes {
636            for attr_qname in attributes.keys() {
637                if !self.is_known_attribute(attr_qname) {
638                    let message = format!("Unknown attribute not allowed: {}", attr_qname.local_name);
639                    if self.policy.strict_mode {
640                        result.add_error(AttributeValidationError::PolicyViolation {
641                            attribute: attr_qname.clone(),
642                            policy: message.clone(),
643                        });
644                    } else {
645                        result.add_warning(message);
646                    }
647                }
648            }
649        }
650    }
651    
652    fn validate_namespace_compliance(&self, attr_qname: &QName, result: &mut ValidationResult) {
653        if let Some(namespace_uri) = &attr_qname.namespace_uri {
654            if let Some(policy) = self.policy.namespace_policies.get(namespace_uri) {
655                if !policy.allowed {
656                    result.add_error(AttributeValidationError::NamespaceValidationFailed {
657                        attribute: attr_qname.clone(),
658                        reason: format!("Namespace {} not allowed", namespace_uri),
659                    });
660                }
661            }
662        }
663    }
664    
665    // Helper methods
666    
667    fn get_attribute_type(&self, value: &AttributeValue) -> AttributeType {
668        match value {
669            AttributeValue::String(_) => AttributeType::String,
670            AttributeValue::Boolean(_) => AttributeType::Boolean,
671            AttributeValue::Integer(_) => AttributeType::Integer,
672            AttributeValue::Decimal(_) => AttributeType::Decimal,
673            AttributeValue::Date(_) => AttributeType::Date,
674            AttributeValue::DateTime(_) => AttributeType::DateTime,
675            AttributeValue::Uri(_) => AttributeType::Uri,
676            AttributeValue::Language(_) => AttributeType::Language,
677            AttributeValue::Token(_) => AttributeType::Token,
678            AttributeValue::Duration(_) => AttributeType::Raw,
679            AttributeValue::Enum(_, _) => AttributeType::String,
680            AttributeValue::Raw(_) => AttributeType::Raw,
681        }
682    }
683    
684    fn get_or_compile_regex(&mut self, pattern: &str) -> Result<&Regex, AttributeValidationError> {
685        if !self.regex_cache.contains_key(pattern) {
686            let regex = Regex::new(pattern).map_err(|e| {
687                AttributeValidationError::InvalidFormat {
688                    attribute: QName::new("regex".to_string()),
689                    reason: format!("Invalid regex pattern: {}", e),
690                }
691            })?;
692            self.regex_cache.insert(pattern.to_string(), regex);
693        }
694        Ok(self.regex_cache.get(pattern).unwrap())
695    }
696    
697    fn validate_numeric_range(
698        &self,
699        attr_qname: &QName,
700        attr_value: &AttributeValue,
701        min: Option<i64>,
702        max: Option<i64>,
703    ) -> Result<(), AttributeValidationError> {
704        let numeric_value = match attr_value {
705            AttributeValue::Integer(i) => *i,
706            AttributeValue::Decimal(d) => *d as i64,
707            _ => return Err(AttributeValidationError::InvalidFormat {
708                attribute: attr_qname.clone(),
709                reason: "not a numeric value".to_string(),
710            }),
711        };
712        
713        if let Some(min_val) = min {
714            if numeric_value < min_val {
715                return Err(AttributeValidationError::ValueOutOfRange {
716                    attribute: attr_qname.clone(),
717                    value: numeric_value.to_string(),
718                    range: format!(">= {}", min_val),
719                });
720            }
721        }
722        
723        if let Some(max_val) = max {
724            if numeric_value > max_val {
725                return Err(AttributeValidationError::ValueOutOfRange {
726                    attribute: attr_qname.clone(),
727                    value: numeric_value.to_string(),
728                    range: format!("<= {}", max_val),
729                });
730            }
731        }
732        
733        Ok(())
734    }
735    
736    fn validate_string_length(
737        &self,
738        attr_qname: &QName,
739        attr_value: &AttributeValue,
740        min: Option<usize>,
741        max: Option<usize>,
742    ) -> Result<(), AttributeValidationError> {
743        let string_value = attr_value.to_string();
744        let length = string_value.len();
745        
746        if let Some(min_len) = min {
747            if length < min_len {
748                return Err(AttributeValidationError::ValueOutOfRange {
749                    attribute: attr_qname.clone(),
750                    value: length.to_string(),
751                    range: format!(">= {} characters", min_len),
752                });
753            }
754        }
755        
756        if let Some(max_len) = max {
757            if length > max_len {
758                return Err(AttributeValidationError::ValueOutOfRange {
759                    attribute: attr_qname.clone(),
760                    value: length.to_string(),
761                    range: format!("<= {} characters", max_len),
762                });
763            }
764        }
765        
766        Ok(())
767    }
768    
769    fn is_known_attribute(&self, attr_qname: &QName) -> bool {
770        self.global_rules.contains_key(attr_qname) ||
771            self.element_rules.values()
772                .any(|element_rules| element_rules.contains_key(attr_qname))
773    }
774    
775    // Setup methods for DDEX and XML Schema rules
776    
777    fn setup_ddex_rules(&mut self) {
778        // DDEX-specific validation rules
779        self.add_global_rule(
780            QName::new("TerritoryCode".to_string()),
781            ValidationRule::Custom("ddex_territory_code".to_string()),
782        );
783        
784        self.add_global_rule(
785            QName::new("LanguageAndScriptCode".to_string()),
786            ValidationRule::Custom("ddex_language_code".to_string()),
787        );
788        
789        self.add_global_rule(
790            QName::new("CurrencyCode".to_string()),
791            ValidationRule::Custom("ddex_currency_code".to_string()),
792        );
793        
794        // Sequence number validation
795        self.add_global_rule(
796            QName::new("SequenceNumber".to_string()),
797            ValidationRule::Range { min: Some(1), max: Some(999999) },
798        );
799        
800        // DDEX identifier patterns
801        self.add_global_rule(
802            QName::new("ISRC".to_string()),
803            ValidationRule::Regex(r"^[A-Z]{2}[A-Z0-9]{3}[0-9]{7}$".to_string()),
804        );
805        
806        self.add_global_rule(
807            QName::new("ISWC".to_string()),
808            ValidationRule::Regex(r"^T-[0-9]{9}-[0-9]$".to_string()),
809        );
810    }
811    
812    fn setup_xml_schema_rules(&mut self) {
813        // XML Schema instance attributes
814        let xsi_ns = "http://www.w3.org/2001/XMLSchema-instance";
815        
816        self.add_global_rule(
817            QName::with_namespace("type".to_string(), xsi_ns.to_string()),
818            ValidationRule::Type(AttributeType::Token),
819        );
820        
821        self.add_global_rule(
822            QName::with_namespace("nil".to_string(), xsi_ns.to_string()),
823            ValidationRule::Type(AttributeType::Boolean),
824        );
825        
826        self.add_global_rule(
827            QName::with_namespace("schemaLocation".to_string(), xsi_ns.to_string()),
828            ValidationRule::Type(AttributeType::Uri),
829        );
830    }
831    
832    fn setup_custom_validators(&mut self) {
833        self.add_custom_validator("ddex_territory_code".to_string(), Self::validate_territory_code);
834        self.add_custom_validator("ddex_language_code".to_string(), Self::validate_language_code);
835        self.add_custom_validator("ddex_currency_code".to_string(), Self::validate_currency_code);
836    }
837    
838    // Custom validation functions
839    
840    fn validate_territory_code(value: &AttributeValue) -> Result<(), String> {
841        let code = value.to_string();
842        // ISO 3166-1 alpha-2 country codes (simplified validation)
843        if code.len() != 2 || !code.chars().all(|c| c.is_ascii_uppercase()) {
844            Err("Invalid territory code format, expected 2 uppercase letters".to_string())
845        } else {
846            Ok(())
847        }
848    }
849    
850    fn validate_language_code(value: &AttributeValue) -> Result<(), String> {
851        let code = value.to_string();
852        // Simplified language code validation (ISO 639-1)
853        if code.len() < 2 || code.len() > 8 {
854            Err("Invalid language code format".to_string())
855        } else {
856            Ok(())
857        }
858    }
859    
860    fn validate_currency_code(value: &AttributeValue) -> Result<(), String> {
861        let code = value.to_string();
862        // ISO 4217 currency codes
863        if code.len() != 3 || !code.chars().all(|c| c.is_ascii_uppercase()) {
864            Err("Invalid currency code format, expected 3 uppercase letters".to_string())
865        } else {
866            Ok(())
867        }
868    }
869}
870
871impl Default for AttributeValidator {
872    fn default() -> Self {
873        Self::new()
874    }
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880    
881    #[test]
882    fn test_basic_validation() {
883        let mut validator = AttributeValidator::new();
884        
885        // Add required attribute rule
886        let attr_name = QName::new("required_attr".to_string());
887        validator.add_global_rule(attr_name.clone(), ValidationRule::Required);
888        
889        let attributes = AttributeMap::new();
890        let result = validator.validate_global_attributes(&attributes);
891        
892        // Should not fail since we're not testing element-specific validation
893        assert!(result.is_valid);
894    }
895    
896    #[test]
897    fn test_enum_validation() {
898        let mut validator = AttributeValidator::new();
899        
900        let attr_name = QName::new("enum_attr".to_string());
901        validator.add_global_rule(
902            attr_name.clone(),
903            ValidationRule::Enum(vec!["value1".to_string(), "value2".to_string()]),
904        );
905        
906        let mut attributes = AttributeMap::new();
907        attributes.insert(attr_name.clone(), AttributeValue::String("value1".to_string()));
908        
909        let result = validator.validate_global_attributes(&attributes);
910        assert!(result.is_valid);
911        
912        // Test invalid enum value
913        attributes.insert(attr_name.clone(), AttributeValue::String("invalid".to_string()));
914        let result = validator.validate_global_attributes(&attributes);
915        assert!(!result.is_valid);
916        assert_eq!(result.errors.len(), 1);
917    }
918    
919    #[test]
920    fn test_regex_validation() {
921        let mut validator = AttributeValidator::new();
922        
923        let attr_name = QName::new("pattern_attr".to_string());
924        validator.add_global_rule(
925            attr_name.clone(),
926            ValidationRule::Regex(r"^\d{4}-\d{2}-\d{2}$".to_string()),
927        );
928        
929        let mut attributes = AttributeMap::new();
930        attributes.insert(attr_name.clone(), AttributeValue::String("2023-12-25".to_string()));
931        
932        let result = validator.validate_global_attributes(&attributes);
933        assert!(result.is_valid);
934        
935        // Test invalid pattern
936        attributes.insert(attr_name.clone(), AttributeValue::String("invalid-date".to_string()));
937        let result = validator.validate_global_attributes(&attributes);
938        assert!(!result.is_valid);
939    }
940    
941    #[test]
942    fn test_uri_validation() {
943        let mut validator = AttributeValidator::new();
944        
945        let attr_name = QName::new("uri_attr".to_string());
946        validator.add_global_rule(attr_name.clone(), ValidationRule::Uri);
947        
948        let mut attributes = AttributeMap::new();
949        attributes.insert(attr_name.clone(), AttributeValue::String("https://example.com".to_string()));
950        
951        let result = validator.validate_global_attributes(&attributes);
952        assert!(result.is_valid);
953        
954        // Test invalid URI
955        attributes.insert(attr_name.clone(), AttributeValue::String("not-a-uri".to_string()));
956        let result = validator.validate_global_attributes(&attributes);
957        assert!(!result.is_valid);
958    }
959    
960    #[test]
961    fn test_range_validation() {
962        let mut validator = AttributeValidator::new();
963        
964        let attr_name = QName::new("numeric_attr".to_string());
965        validator.add_global_rule(
966            attr_name.clone(),
967            ValidationRule::Range { min: Some(1), max: Some(100) },
968        );
969        
970        let mut attributes = AttributeMap::new();
971        attributes.insert(attr_name.clone(), AttributeValue::Integer(50));
972        
973        let result = validator.validate_global_attributes(&attributes);
974        assert!(result.is_valid);
975        
976        // Test out of range
977        attributes.insert(attr_name.clone(), AttributeValue::Integer(150));
978        let result = validator.validate_global_attributes(&attributes);
979        assert!(!result.is_valid);
980    }
981    
982    #[test]
983    fn test_custom_validation() {
984        let mut validator = AttributeValidator::new();
985        
986        let attr_name = QName::new("TerritoryCode".to_string());
987        // Rule already added in setup_ddex_rules
988        
989        let mut attributes = AttributeMap::new();
990        attributes.insert(attr_name.clone(), AttributeValue::String("US".to_string()));
991        
992        let result = validator.validate_global_attributes(&attributes);
993        assert!(result.is_valid);
994        
995        // Test invalid territory code
996        attributes.insert(attr_name.clone(), AttributeValue::String("invalid".to_string()));
997        let result = validator.validate_global_attributes(&attributes);
998        assert!(!result.is_valid);
999    }
1000}