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