use crate::error::{ValidationError, ValidationResult};
use crate::provider::{OptionDef, OptionProvider};
use crate::suggestion::OptionSuggester;
use crate::validation::ValidationConfig;
use std::collections::HashMap;
pub struct ValidationRegistry {
providers: Vec<Box<dyn OptionProvider>>,
suggester: OptionSuggester,
}
impl Default for ValidationRegistry {
fn default() -> Self {
Self::new()
}
}
impl ValidationRegistry {
pub fn new() -> Self {
Self {
providers: Vec::new(),
suggester: OptionSuggester::new(),
}
}
pub fn register_provider(&mut self, provider: Box<dyn OptionProvider>) {
self.providers.push(provider);
}
pub fn get_providers(&self) -> &[Box<dyn OptionProvider>] {
&self.providers
}
pub fn get_options(&self, command: &str) -> Vec<OptionDef> {
let mut all_options = Vec::new();
for provider in &self.providers {
if provider.supports_command(command) {
all_options.extend(provider.get_options(command));
}
}
let mut seen = std::collections::HashSet::new();
all_options.retain(|opt| seen.insert(opt.name.clone()));
all_options
}
pub fn validate_option(
&self,
command: &str,
option: &str,
value: Option<&str>,
config: &ValidationConfig,
) -> ValidationResult<()> {
for provider in &self.providers {
if provider.supports_command(command) {
let options = provider.get_options(command);
if options.iter().any(|opt| opt.name == option) {
return provider.validate(command, option, value);
}
}
}
if config.level.is_strict() {
let suggestions = self.suggest_option(command, option);
Err(ValidationError::unknown_option(
command,
option,
suggestions,
))
} else {
Ok(())
}
}
pub fn suggest_option(&self, command: &str, option: &str) -> Vec<String> {
let all_options = self.get_options(command);
let option_names: Vec<&str> = all_options.iter().map(|opt| opt.name.as_str()).collect();
self.suggester.suggest_options(option, &option_names)
}
pub fn validate_conflicts(
&self,
command: &str,
options: &HashMap<String, Option<String>>,
) -> ValidationResult<()> {
let option_defs = self.get_options(command);
let mut option_map: HashMap<String, &OptionDef> = HashMap::new();
for def in &option_defs {
option_map.insert(def.name.clone(), def);
}
for option_name in options.keys() {
if let Some(option_def) = option_map.get(option_name) {
for conflict in &option_def.conflicts_with {
if options.contains_key(conflict) {
return Err(ValidationError::conflict(option_name, conflict));
}
}
}
}
Ok(())
}
pub fn validate_requirements(
&self,
command: &str,
options: &HashMap<String, Option<String>>,
) -> ValidationResult<()> {
let option_defs = self.get_options(command);
let mut option_map: HashMap<String, &OptionDef> = HashMap::new();
for def in &option_defs {
option_map.insert(def.name.clone(), def);
}
for option_name in options.keys() {
if let Some(option_def) = option_map.get(option_name) {
for required in &option_def.requires {
if !options.contains_key(required) {
return Err(ValidationError::missing_requirement(option_name, required));
}
}
}
}
Ok(())
}
pub fn get_provider(&self, name: &str) -> Option<&dyn OptionProvider> {
self.providers
.iter()
.find(|p| p.name() == name)
.map(|p| p.as_ref())
}
pub fn supports_command(&self, command: &str) -> bool {
self.providers.iter().any(|p| p.supports_command(command))
}
}