Skip to main content

fraiseql_core/validation/
inheritance.rs

1//! Validation inheritance for input types.
2//!
3//! This module provides support for validation rule inheritance, allowing child
4//! input types to inherit and override validation rules from parent types.
5//!
6//! # Examples
7//!
8//! ```ignore
9//! // Parent: UserInput with required email and minLength 5
10//! // Child: AdminUserInput extends UserInput with additional admin-only rules
11//!
12//! let parent_rules = vec![
13//!     ValidationRule::Pattern { pattern: "^.+@.+$".to_string(), message: None },
14//!     ValidationRule::Length { min: Some(5), max: None }
15//! ];
16//!
17//! let child_rules = vec![
18//!     ValidationRule::Required,
19//!     ValidationRule::Pattern { pattern: "^admin_.+$".to_string(), message: None }
20//! ];
21//!
22//! let inherited = inherit_validation_rules(&parent_rules, &child_rules, InheritanceMode::Merge);
23//! ```
24
25use std::collections::HashMap;
26
27use crate::validation::rules::ValidationRule;
28
29/// Determines how child validation rules interact with parent rules.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum InheritanceMode {
32    /// Child rules completely override parent rules (no inheritance)
33    Override,
34    /// Parent and child rules are merged (all apply)
35    Merge,
36    /// Child rules are applied first, then parent rules
37    ChildFirst,
38    /// Parent rules are applied first, then child rules
39    ParentFirst,
40}
41
42impl InheritanceMode {
43    /// Get a human-readable description.
44    pub fn description(&self) -> &'static str {
45        match self {
46            Self::Override => "Child rules override parent rules completely",
47            Self::Merge => "All parent and child rules apply (union)",
48            Self::ChildFirst => "Child rules applied first, then parent rules",
49            Self::ParentFirst => "Parent rules applied first, then child rules",
50        }
51    }
52}
53
54/// Metadata about a validation rule for inheritance tracking.
55#[derive(Debug, Clone)]
56pub struct RuleMetadata {
57    /// The validation rule
58    pub rule:         ValidationRule,
59    /// Whether this rule can be overridden by child types
60    pub overrideable: bool,
61    /// Whether this rule is inherited from a parent type
62    pub inherited:    bool,
63    /// The source type name for tracking
64    pub source:       String,
65}
66
67impl RuleMetadata {
68    /// Create a new rule metadata from a validation rule.
69    pub fn new(rule: ValidationRule, source: impl Into<String>) -> Self {
70        Self {
71            rule,
72            overrideable: true,
73            inherited: false,
74            source: source.into(),
75        }
76    }
77
78    /// Mark this rule as non-overrideable.
79    pub fn non_overrideable(mut self) -> Self {
80        self.overrideable = false;
81        self
82    }
83
84    /// Mark this rule as inherited.
85    pub fn as_inherited(mut self) -> Self {
86        self.inherited = true;
87        self
88    }
89}
90
91/// Validation rule registry tracking inheritance relationships.
92#[derive(Debug, Clone, Default)]
93pub struct ValidationRuleRegistry {
94    /// Rules by type name
95    rules_by_type: HashMap<String, Vec<RuleMetadata>>,
96    /// Parent type references
97    parent_types:  HashMap<String, String>,
98}
99
100impl ValidationRuleRegistry {
101    /// Create a new validation rule registry.
102    pub fn new() -> Self {
103        Self {
104            rules_by_type: HashMap::new(),
105            parent_types:  HashMap::new(),
106        }
107    }
108
109    /// Register rules for a type.
110    pub fn register_type(&mut self, type_name: impl Into<String>, rules: Vec<RuleMetadata>) {
111        self.rules_by_type.insert(type_name.into(), rules);
112    }
113
114    /// Set the parent type for inheritance.
115    pub fn set_parent(&mut self, child_type: impl Into<String>, parent_type: impl Into<String>) {
116        self.parent_types.insert(child_type.into(), parent_type.into());
117    }
118
119    /// Get rules for a type, including inherited rules.
120    pub fn get_rules(&self, type_name: &str, mode: InheritanceMode) -> Vec<RuleMetadata> {
121        let mut rules = Vec::new();
122
123        // Get parent rules if applicable
124        if let Some(parent_name) = self.parent_types.get(type_name) {
125            let parent_rules = self.get_rules(parent_name, mode);
126            rules.extend(parent_rules.iter().map(|r| r.clone().as_inherited()));
127        }
128
129        // Get own rules
130        if let Some(own_rules) = self.rules_by_type.get(type_name) {
131            match mode {
132                InheritanceMode::Override => {
133                    // Child rules completely override parent rules
134                    return own_rules.clone();
135                },
136                InheritanceMode::Merge => {
137                    // Add all own rules that don't override parent rules
138                    for own_rule in own_rules {
139                        rules.push(own_rule.clone());
140                    }
141                },
142                InheritanceMode::ChildFirst => {
143                    // Keep order: own rules first (reverse order since we prepend)
144                    let mut result = own_rules.clone();
145                    result.extend(rules);
146                    return result;
147                },
148                InheritanceMode::ParentFirst => {
149                    // Keep order: parent rules first
150                    rules.extend(own_rules.clone());
151                },
152            }
153        }
154
155        rules
156    }
157
158    /// Get the parent type name if one exists.
159    pub fn get_parent(&self, type_name: &str) -> Option<&str> {
160        self.parent_types.get(type_name).map(|s| s.as_str())
161    }
162
163    /// Check if a type has a parent.
164    pub fn has_parent(&self, type_name: &str) -> bool {
165        self.parent_types.contains_key(type_name)
166    }
167}
168
169/// Inherit validation rules from parent to child.
170///
171/// # Arguments
172/// * `parent_rules` - Rules from the parent type
173/// * `child_rules` - Rules defined on the child type
174/// * `mode` - How to combine parent and child rules
175///
176/// # Returns
177/// Combined rules based on the inheritance mode
178pub fn inherit_validation_rules(
179    parent_rules: &[ValidationRule],
180    child_rules: &[ValidationRule],
181    mode: InheritanceMode,
182) -> Vec<ValidationRule> {
183    match mode {
184        InheritanceMode::Override => {
185            // Child rules completely replace parent rules
186            child_rules.to_vec()
187        },
188        InheritanceMode::Merge => {
189            // Combine all rules from both parent and child
190            let mut combined = parent_rules.to_vec();
191            combined.extend_from_slice(child_rules);
192            combined
193        },
194        InheritanceMode::ChildFirst => {
195            // Child rules first, then parent rules
196            let mut combined = child_rules.to_vec();
197            combined.extend_from_slice(parent_rules);
198            combined
199        },
200        InheritanceMode::ParentFirst => {
201            // Parent rules first, then child rules
202            let mut combined = parent_rules.to_vec();
203            combined.extend_from_slice(child_rules);
204            combined
205        },
206    }
207}
208
209/// Check if child type has valid inheritance from parent type.
210///
211/// # Arguments
212/// * `_child_name` - Name of the child type
213/// * `parent_name` - Name of the parent type
214/// * `registry` - The validation rule registry
215///
216/// # Returns
217/// Ok(()) if inheritance is valid, Err with message if invalid
218pub fn validate_inheritance(
219    _child_name: &str,
220    parent_name: &str,
221    registry: &ValidationRuleRegistry,
222) -> Result<(), String> {
223    // Check that parent type exists in registry
224    if !registry.rules_by_type.contains_key(parent_name) {
225        return Err(format!("Parent type '{}' not found in validation registry", parent_name));
226    }
227
228    // Check for circular inheritance
229    let mut visited = std::collections::HashSet::new();
230    let mut current = Some(parent_name.to_string());
231
232    while let Some(type_name) = current {
233        if visited.contains(&type_name) {
234            return Err(format!(
235                "Circular inheritance detected: '{}' inherits from itself",
236                type_name
237            ));
238        }
239        visited.insert(type_name.clone());
240
241        current = registry.get_parent(&type_name).map(|s| s.to_string());
242    }
243
244    Ok(())
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_override_mode() {
253        let parent = vec![
254            ValidationRule::Required,
255            ValidationRule::Length {
256                min: Some(5),
257                max: None,
258            },
259        ];
260        let child = vec![ValidationRule::Pattern {
261            pattern: "^[a-z]+$".to_string(),
262            message: None,
263        }];
264
265        let result = inherit_validation_rules(&parent, &child, InheritanceMode::Override);
266        assert_eq!(result.len(), 1);
267        assert!(matches!(result[0], ValidationRule::Pattern { .. }));
268    }
269
270    #[test]
271    fn test_merge_mode() {
272        let parent = vec![
273            ValidationRule::Required,
274            ValidationRule::Length {
275                min: Some(5),
276                max: None,
277            },
278        ];
279        let child = vec![ValidationRule::Pattern {
280            pattern: "^[a-z]+$".to_string(),
281            message: None,
282        }];
283
284        let result = inherit_validation_rules(&parent, &child, InheritanceMode::Merge);
285        assert_eq!(result.len(), 3);
286    }
287
288    #[test]
289    fn test_child_first_mode() {
290        let parent = vec![ValidationRule::Required];
291        let child = vec![ValidationRule::Pattern {
292            pattern: "^[a-z]+$".to_string(),
293            message: None,
294        }];
295
296        let result = inherit_validation_rules(&parent, &child, InheritanceMode::ChildFirst);
297        assert_eq!(result.len(), 2);
298        assert!(matches!(result[0], ValidationRule::Pattern { .. }));
299        assert!(matches!(result[1], ValidationRule::Required));
300    }
301
302    #[test]
303    fn test_parent_first_mode() {
304        let parent = vec![ValidationRule::Required];
305        let child = vec![ValidationRule::Pattern {
306            pattern: "^[a-z]+$".to_string(),
307            message: None,
308        }];
309
310        let result = inherit_validation_rules(&parent, &child, InheritanceMode::ParentFirst);
311        assert_eq!(result.len(), 2);
312        assert!(matches!(result[0], ValidationRule::Required));
313        assert!(matches!(result[1], ValidationRule::Pattern { .. }));
314    }
315
316    #[test]
317    fn test_registry_register_type() {
318        let mut registry = ValidationRuleRegistry::new();
319        let rules = vec![RuleMetadata::new(ValidationRule::Required, "UserInput")];
320        registry.register_type("UserInput", rules);
321
322        assert!(registry.rules_by_type.contains_key("UserInput"));
323    }
324
325    #[test]
326    fn test_registry_set_parent() {
327        let mut registry = ValidationRuleRegistry::new();
328        registry.set_parent("AdminUserInput", "UserInput");
329
330        assert_eq!(registry.get_parent("AdminUserInput"), Some("UserInput"));
331    }
332
333    #[test]
334    fn test_registry_has_parent() {
335        let mut registry = ValidationRuleRegistry::new();
336        registry.set_parent("ChildType", "ParentType");
337
338        assert!(registry.has_parent("ChildType"));
339        assert!(!registry.has_parent("ParentType"));
340    }
341
342    #[test]
343    fn test_registry_get_rules_with_merge() {
344        let mut registry = ValidationRuleRegistry::new();
345
346        let parent_rules = vec![RuleMetadata::new(ValidationRule::Required, "UserInput")];
347        registry.register_type("UserInput", parent_rules);
348
349        let child_rules = vec![RuleMetadata::new(
350            ValidationRule::Length {
351                min: Some(5),
352                max: None,
353            },
354            "AdminUserInput",
355        )];
356        registry.register_type("AdminUserInput", child_rules);
357        registry.set_parent("AdminUserInput", "UserInput");
358
359        let inherited = registry.get_rules("AdminUserInput", InheritanceMode::Merge);
360        assert_eq!(inherited.len(), 2);
361    }
362
363    #[test]
364    fn test_registry_get_rules_with_override() {
365        let mut registry = ValidationRuleRegistry::new();
366
367        let parent_rules = vec![RuleMetadata::new(ValidationRule::Required, "UserInput")];
368        registry.register_type("UserInput", parent_rules);
369
370        let child_rules = vec![RuleMetadata::new(
371            ValidationRule::Length {
372                min: Some(5),
373                max: None,
374            },
375            "AdminUserInput",
376        )];
377        registry.register_type("AdminUserInput", child_rules);
378        registry.set_parent("AdminUserInput", "UserInput");
379
380        let inherited = registry.get_rules("AdminUserInput", InheritanceMode::Override);
381        assert_eq!(inherited.len(), 1);
382        assert!(matches!(inherited[0].rule, ValidationRule::Length { .. }));
383    }
384
385    #[test]
386    fn test_validate_inheritance_success() {
387        let mut registry = ValidationRuleRegistry::new();
388        let parent_rules = vec![RuleMetadata::new(ValidationRule::Required, "UserInput")];
389        registry.register_type("UserInput", parent_rules);
390
391        let result = validate_inheritance("AdminUserInput", "UserInput", &registry);
392        assert!(result.is_ok());
393    }
394
395    #[test]
396    fn test_validate_inheritance_parent_not_found() {
397        let registry = ValidationRuleRegistry::new();
398        let result = validate_inheritance("AdminUserInput", "NonExistent", &registry);
399        assert!(result.is_err());
400        assert!(result.unwrap_err().contains("not found"));
401    }
402
403    #[test]
404    fn test_validate_inheritance_circular() {
405        let mut registry = ValidationRuleRegistry::new();
406
407        let user_rules = vec![RuleMetadata::new(ValidationRule::Required, "UserInput")];
408        registry.register_type("UserInput", user_rules);
409
410        let admin_rules = vec![RuleMetadata::new(
411            ValidationRule::Required,
412            "AdminUserInput",
413        )];
414        registry.register_type("AdminUserInput", admin_rules);
415
416        registry.set_parent("UserInput", "AdminUserInput");
417        registry.set_parent("AdminUserInput", "UserInput");
418
419        let result = validate_inheritance("UserInput", "AdminUserInput", &registry);
420        assert!(result.is_err());
421        assert!(result.unwrap_err().contains("Circular"));
422    }
423
424    #[test]
425    fn test_multi_level_inheritance() {
426        let mut registry = ValidationRuleRegistry::new();
427
428        // GrandParent
429        let grandparent_rules = vec![RuleMetadata::new(ValidationRule::Required, "BaseInput")];
430        registry.register_type("BaseInput", grandparent_rules);
431
432        // Parent
433        let parent_rules = vec![RuleMetadata::new(
434            ValidationRule::Length {
435                min: Some(5),
436                max: None,
437            },
438            "UserInput",
439        )];
440        registry.register_type("UserInput", parent_rules);
441        registry.set_parent("UserInput", "BaseInput");
442
443        // Child
444        let child_rules = vec![RuleMetadata::new(
445            ValidationRule::Pattern {
446                pattern: "^[a-z]+$".to_string(),
447                message: None,
448            },
449            "AdminUserInput",
450        )];
451        registry.register_type("AdminUserInput", child_rules);
452        registry.set_parent("AdminUserInput", "UserInput");
453
454        let inherited = registry.get_rules("AdminUserInput", InheritanceMode::Merge);
455        // Should have: grandparent rule + parent rule + child rule
456        assert_eq!(inherited.len(), 3);
457    }
458
459    #[test]
460    fn test_rule_metadata_non_overrideable() {
461        let rule = RuleMetadata::new(ValidationRule::Required, "UserInput").non_overrideable();
462        assert!(!rule.overrideable);
463        assert!(!rule.inherited);
464    }
465
466    #[test]
467    fn test_rule_metadata_as_inherited() {
468        let mut rule = RuleMetadata::new(ValidationRule::Required, "UserInput");
469        rule = rule.as_inherited();
470        assert!(rule.inherited);
471        assert!(rule.overrideable);
472    }
473
474    #[test]
475    fn test_inheritance_mode_description() {
476        assert!(!InheritanceMode::Override.description().is_empty());
477        assert!(!InheritanceMode::Merge.description().is_empty());
478        assert!(!InheritanceMode::ChildFirst.description().is_empty());
479        assert!(!InheritanceMode::ParentFirst.description().is_empty());
480    }
481
482    #[test]
483    fn test_complex_inheritance_scenario() {
484        let mut registry = ValidationRuleRegistry::new();
485
486        // Base: email + minLength 5
487        let base_rules = vec![
488            RuleMetadata::new(ValidationRule::Required, "BaseInput"),
489            RuleMetadata::new(
490                ValidationRule::Length {
491                    min: Some(5),
492                    max: None,
493                },
494                "BaseInput",
495            ),
496        ];
497        registry.register_type("BaseInput", base_rules);
498
499        // User extends Base: adds pattern
500        let user_rules = vec![RuleMetadata::new(
501            ValidationRule::Pattern {
502                pattern: "^[a-z]+$".to_string(),
503                message: None,
504            },
505            "UserInput",
506        )];
507        registry.register_type("UserInput", user_rules);
508        registry.set_parent("UserInput", "BaseInput");
509
510        // Admin extends User: adds enum constraint
511        let admin_rules = vec![RuleMetadata::new(
512            ValidationRule::Enum {
513                values: vec!["admin".to_string(), "moderator".to_string()],
514            },
515            "AdminUserInput",
516        )];
517        registry.register_type("AdminUserInput", admin_rules);
518        registry.set_parent("AdminUserInput", "UserInput");
519
520        let inherited = registry.get_rules("AdminUserInput", InheritanceMode::Merge);
521        // Should have all rules: 2 from base + 1 from user + 1 from admin = 4
522        assert_eq!(inherited.len(), 4);
523    }
524
525    #[test]
526    fn test_empty_child_rules() {
527        let parent = vec![ValidationRule::Required];
528        let child: Vec<ValidationRule> = vec![];
529
530        let result = inherit_validation_rules(&parent, &child, InheritanceMode::Merge);
531        assert_eq!(result.len(), 1);
532    }
533
534    #[test]
535    fn test_empty_parent_rules() {
536        let parent: Vec<ValidationRule> = vec![];
537        let child = vec![ValidationRule::Required];
538
539        let result = inherit_validation_rules(&parent, &child, InheritanceMode::Merge);
540        assert_eq!(result.len(), 1);
541    }
542
543    #[test]
544    fn test_empty_both_rules() {
545        let parent: Vec<ValidationRule> = vec![];
546        let child: Vec<ValidationRule> = vec![];
547
548        let result = inherit_validation_rules(&parent, &child, InheritanceMode::Merge);
549        assert!(result.is_empty());
550    }
551}