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::{CompiledPattern, 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: CompiledPattern::new("^.+@.+$").expect("valid regex"), message: None },
16//! ValidationRule::Length { min: Some(5), max: None },
17//! ];
18//!
19//! let child_rules = vec![
20//! ValidationRule::Required,
21//! ValidationRule::Pattern { pattern: CompiledPattern::new("^admin_.+$").expect("valid regex"), 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 #[must_use]
49 pub const fn description(&self) -> &'static str {
50 match self {
51 Self::Override => "Child rules override parent rules completely",
52 Self::Merge => "All parent and child rules apply (union)",
53 Self::ChildFirst => "Child rules applied first, then parent rules",
54 Self::ParentFirst => "Parent rules applied first, then child rules",
55 }
56 }
57}
58
59/// Metadata about a validation rule for inheritance tracking.
60#[derive(Debug, Clone)]
61pub struct RuleMetadata {
62 /// The validation rule
63 pub rule: ValidationRule,
64 /// Whether this rule can be overridden by child types
65 pub overrideable: bool,
66 /// Whether this rule is inherited from a parent type
67 pub inherited: bool,
68 /// The source type name for tracking
69 pub source: String,
70}
71
72impl RuleMetadata {
73 /// Create a new rule metadata from a validation rule.
74 pub fn new(rule: ValidationRule, source: impl Into<String>) -> Self {
75 Self {
76 rule,
77 overrideable: true,
78 inherited: false,
79 source: source.into(),
80 }
81 }
82
83 /// Mark this rule as non-overrideable.
84 #[must_use]
85 pub const fn non_overrideable(mut self) -> Self {
86 self.overrideable = false;
87 self
88 }
89
90 /// Mark this rule as inherited.
91 #[must_use]
92 pub const fn as_inherited(mut self) -> Self {
93 self.inherited = true;
94 self
95 }
96}
97
98/// Validation rule registry tracking inheritance relationships.
99#[derive(Debug, Clone, Default)]
100pub struct ValidationRuleRegistry {
101 /// Rules by type name
102 pub(crate) rules_by_type: HashMap<String, Vec<RuleMetadata>>,
103 /// Parent type references
104 parent_types: HashMap<String, String>,
105}
106
107impl ValidationRuleRegistry {
108 /// Create a new validation rule registry.
109 #[must_use]
110 pub fn new() -> Self {
111 Self {
112 rules_by_type: HashMap::new(),
113 parent_types: HashMap::new(),
114 }
115 }
116
117 /// Register rules for a type.
118 pub fn register_type(&mut self, type_name: impl Into<String>, rules: Vec<RuleMetadata>) {
119 self.rules_by_type.insert(type_name.into(), rules);
120 }
121
122 /// Set the parent type for inheritance.
123 pub fn set_parent(&mut self, child_type: impl Into<String>, parent_type: impl Into<String>) {
124 self.parent_types.insert(child_type.into(), parent_type.into());
125 }
126
127 /// Get rules for a type, including inherited rules.
128 #[must_use]
129 pub fn get_rules(&self, type_name: &str, mode: InheritanceMode) -> Vec<RuleMetadata> {
130 let mut rules = Vec::new();
131
132 // Get parent rules if applicable
133 if let Some(parent_name) = self.parent_types.get(type_name) {
134 let parent_rules = self.get_rules(parent_name, mode);
135 rules.extend(parent_rules.iter().map(|r| r.clone().as_inherited()));
136 }
137
138 // Get own rules
139 if let Some(own_rules) = self.rules_by_type.get(type_name) {
140 match mode {
141 InheritanceMode::Override => {
142 // Child rules completely override parent rules
143 return own_rules.clone();
144 },
145 InheritanceMode::Merge => {
146 // Add all own rules that don't override parent rules
147 for own_rule in own_rules {
148 rules.push(own_rule.clone());
149 }
150 },
151 InheritanceMode::ChildFirst => {
152 // Keep order: own rules first (reverse order since we prepend)
153 let mut result = own_rules.clone();
154 result.extend(rules);
155 return result;
156 },
157 InheritanceMode::ParentFirst => {
158 // Keep order: parent rules first
159 rules.extend(own_rules.clone());
160 },
161 }
162 }
163
164 rules
165 }
166
167 /// Get the parent type name if one exists.
168 #[must_use]
169 pub fn get_parent(&self, type_name: &str) -> Option<&str> {
170 self.parent_types.get(type_name).map(|s| s.as_str())
171 }
172
173 /// Check if a type has a parent.
174 #[must_use]
175 pub fn has_parent(&self, type_name: &str) -> bool {
176 self.parent_types.contains_key(type_name)
177 }
178}
179
180/// Inherit validation rules from parent to child.
181///
182/// # Arguments
183/// * `parent_rules` - Rules from the parent type
184/// * `child_rules` - Rules defined on the child type
185/// * `mode` - How to combine parent and child rules
186///
187/// # Returns
188/// Combined rules based on the inheritance mode
189#[must_use]
190pub fn inherit_validation_rules(
191 parent_rules: &[ValidationRule],
192 child_rules: &[ValidationRule],
193 mode: InheritanceMode,
194) -> Vec<ValidationRule> {
195 match mode {
196 InheritanceMode::Override => {
197 // Child rules completely replace parent rules
198 child_rules.to_vec()
199 },
200 InheritanceMode::Merge => {
201 // Combine all rules from both parent and child
202 let mut combined = parent_rules.to_vec();
203 combined.extend_from_slice(child_rules);
204 combined
205 },
206 InheritanceMode::ChildFirst => {
207 // Child rules first, then parent rules
208 let mut combined = child_rules.to_vec();
209 combined.extend_from_slice(parent_rules);
210 combined
211 },
212 InheritanceMode::ParentFirst => {
213 // Parent rules first, then child rules
214 let mut combined = parent_rules.to_vec();
215 combined.extend_from_slice(child_rules);
216 combined
217 },
218 }
219}
220
221/// Check if child type has valid inheritance from parent type.
222///
223/// # Arguments
224/// * `_child_name` - Name of the child type
225/// * `parent_name` - Name of the parent type
226/// * `registry` - The validation rule registry
227///
228/// # Errors
229///
230/// Returns an error string if the parent type is not found or contains circular inheritance.
231pub fn validate_inheritance(
232 _child_name: &str,
233 parent_name: &str,
234 registry: &ValidationRuleRegistry,
235) -> Result<(), String> {
236 // Check that parent type exists in registry
237 if !registry.rules_by_type.contains_key(parent_name) {
238 return Err(format!("Parent type '{}' not found in validation registry", parent_name));
239 }
240
241 // Check for circular inheritance
242 let mut visited = std::collections::HashSet::new();
243 let mut current = Some(parent_name.to_string());
244
245 while let Some(type_name) = current {
246 if visited.contains(&type_name) {
247 return Err(format!(
248 "Circular inheritance detected: '{}' inherits from itself",
249 type_name
250 ));
251 }
252 visited.insert(type_name.clone());
253
254 current = registry.get_parent(&type_name).map(|s| s.to_string());
255 }
256
257 Ok(())
258}