use std::path::{Path, PathBuf};
use thiserror::Error;
use super::merge::Merge;
use super::types::Config;
use super::validator::ConfigValidator;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Failed to read config file: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse config: {0}")]
Parse(#[from] toml::de::Error),
#[error("Config file not found: {0}")]
NotFound(PathBuf),
#[error("Invalid configuration: {0}")]
Invalid(String),
#[error("{0}")]
Validation(#[from] super::types::ConfigValidationError),
}
#[derive(Debug)]
pub struct ConfigLoader {
files: Vec<PathBuf>,
validate: bool,
validator: Option<ConfigValidator>,
}
impl Default for ConfigLoader {
fn default() -> Self {
Self::new()
}
}
impl ConfigLoader {
pub fn new() -> Self {
Self {
files: Vec::new(),
validate: false,
validator: None,
}
}
pub fn file<P: AsRef<Path>>(mut self, path: P) -> Self {
self.files.push(path.as_ref().to_path_buf());
self
}
pub fn files<I, P>(mut self, paths: I) -> Self
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
self.files
.extend(paths.into_iter().map(|p| p.as_ref().to_path_buf()));
self
}
pub fn with_validation(mut self, validate: bool) -> Self {
self.validate = validate;
self
}
pub fn with_validator(mut self, validator: ConfigValidator) -> Self {
self.validator = Some(validator);
self
}
pub fn load(self) -> Result<Config, ConfigError> {
let mut config = Config::default();
for path in &self.files {
if path.exists() {
let content = std::fs::read_to_string(path)?;
let file_config: Config = toml::from_str(&content)?;
config.merge(&file_config, super::merge::MergeStrategy::Replace);
} else {
return Err(ConfigError::NotFound(path.clone()));
}
}
if self.validate {
let validator = self.validator.unwrap_or_default();
validator.validate(&config)?;
}
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.indexer.subsection_threshold, 300);
assert!(config.summary.model.is_empty());
assert!(config.retrieval.model.is_empty());
}
#[test]
fn test_config_loader_defaults() {
let config = ConfigLoader::new().load().unwrap();
assert_eq!(config.indexer.subsection_threshold, 300);
}
#[test]
fn test_config_loader_not_found() {
let result = ConfigLoader::new().file("nonexistent_config.toml").load();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::NotFound(_)));
}
#[test]
fn test_config_loader_with_validation() {
let config = ConfigLoader::new().with_validation(true).load().unwrap();
assert!(config.retrieval.model.is_empty());
}
}