raz_validation/
registry.rs

1//! Validation registry for managing option providers
2
3use crate::error::{ValidationError, ValidationResult};
4use crate::provider::{OptionDef, OptionProvider};
5use crate::suggestion::OptionSuggester;
6use crate::validation::ValidationConfig;
7use std::collections::HashMap;
8
9/// Registry that manages multiple option providers
10pub struct ValidationRegistry {
11    providers: Vec<Box<dyn OptionProvider>>,
12    suggester: OptionSuggester,
13}
14
15impl Default for ValidationRegistry {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl ValidationRegistry {
22    /// Create a new empty registry
23    pub fn new() -> Self {
24        Self {
25            providers: Vec::new(),
26            suggester: OptionSuggester::new(),
27        }
28    }
29
30    /// Register a new option provider
31    pub fn register_provider(&mut self, provider: Box<dyn OptionProvider>) {
32        self.providers.push(provider);
33    }
34
35    /// Get all providers
36    pub fn get_providers(&self) -> &[Box<dyn OptionProvider>] {
37        &self.providers
38    }
39
40    /// Get options for a command from all applicable providers
41    pub fn get_options(&self, command: &str) -> Vec<OptionDef> {
42        let mut all_options = Vec::new();
43
44        for provider in &self.providers {
45            if provider.supports_command(command) {
46                all_options.extend(provider.get_options(command));
47            }
48        }
49
50        // Remove duplicates (prefer first occurrence)
51        let mut seen = std::collections::HashSet::new();
52        all_options.retain(|opt| seen.insert(opt.name.clone()));
53
54        all_options
55    }
56
57    /// Validate an option using the appropriate provider
58    pub fn validate_option(
59        &self,
60        command: &str,
61        option: &str,
62        value: Option<&str>,
63        config: &ValidationConfig,
64    ) -> ValidationResult<()> {
65        // Find a provider that supports this command and has this option
66        for provider in &self.providers {
67            if provider.supports_command(command) {
68                let options = provider.get_options(command);
69                if options.iter().any(|opt| opt.name == option) {
70                    return provider.validate(command, option, value);
71                }
72            }
73        }
74
75        // No provider recognized this option
76        if config.level.is_strict() {
77            let suggestions = self.suggest_option(command, option);
78            Err(ValidationError::unknown_option(
79                command,
80                option,
81                suggestions,
82            ))
83        } else {
84            // In non-strict mode, allow unknown options
85            Ok(())
86        }
87    }
88
89    /// Get suggestions for a misspelled option
90    pub fn suggest_option(&self, command: &str, option: &str) -> Vec<String> {
91        let all_options = self.get_options(command);
92        let option_names: Vec<&str> = all_options.iter().map(|opt| opt.name.as_str()).collect();
93
94        self.suggester.suggest_options(option, &option_names)
95    }
96
97    /// Validate conflicts between options
98    pub fn validate_conflicts(
99        &self,
100        command: &str,
101        options: &HashMap<String, Option<String>>,
102    ) -> ValidationResult<()> {
103        let option_defs = self.get_options(command);
104        let mut option_map: HashMap<String, &OptionDef> = HashMap::new();
105
106        for def in &option_defs {
107            option_map.insert(def.name.clone(), def);
108        }
109
110        // Check for conflicts
111        for option_name in options.keys() {
112            if let Some(option_def) = option_map.get(option_name) {
113                for conflict in &option_def.conflicts_with {
114                    if options.contains_key(conflict) {
115                        return Err(ValidationError::conflict(option_name, conflict));
116                    }
117                }
118            }
119        }
120
121        Ok(())
122    }
123
124    /// Validate required options
125    pub fn validate_requirements(
126        &self,
127        command: &str,
128        options: &HashMap<String, Option<String>>,
129    ) -> ValidationResult<()> {
130        let option_defs = self.get_options(command);
131        let mut option_map: HashMap<String, &OptionDef> = HashMap::new();
132
133        for def in &option_defs {
134            option_map.insert(def.name.clone(), def);
135        }
136
137        // Check for missing requirements
138        for option_name in options.keys() {
139            if let Some(option_def) = option_map.get(option_name) {
140                for required in &option_def.requires {
141                    if !options.contains_key(required) {
142                        return Err(ValidationError::missing_requirement(option_name, required));
143                    }
144                }
145            }
146        }
147
148        Ok(())
149    }
150
151    /// Get provider by name
152    pub fn get_provider(&self, name: &str) -> Option<&dyn OptionProvider> {
153        self.providers
154            .iter()
155            .find(|p| p.name() == name)
156            .map(|p| p.as_ref())
157    }
158
159    /// Check if any provider supports a command
160    pub fn supports_command(&self, command: &str) -> bool {
161        self.providers.iter().any(|p| p.supports_command(command))
162    }
163}