use crate::arf::types::*;
use crate::error::Result;
use config::{Config as ConfigLoader, File};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct Config {
pub inner: ArfConfig,
}
impl Config {
pub fn load() -> Result<Self> {
Self::load_from_paths(&[
"arf.toml",
"arf.yaml",
"arf.json",
"/etc/arf/config.toml",
"/etc/arf/config.yaml",
"/etc/arf/config.json",
])
}
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
if path_ref.extension().is_none() {
let contents = std::fs::read_to_string(path_ref)?;
if let Ok(config) = serde_json::from_str::<ArfConfig>(&contents) {
return Ok(Self { inner: config });
}
if let Ok(config) = toml::from_str::<ArfConfig>(&contents) {
return Ok(Self { inner: config });
}
return Err(crate::error::Error::config(
"Failed to parse extensionless config file".to_string(),
));
}
let loader = ConfigLoader::builder().add_source(File::from(path_ref));
let config: ArfConfig = loader.build()?.try_deserialize()?;
Ok(Self { inner: config })
}
pub fn load_from_paths<P: AsRef<Path>>(paths: &[P]) -> Result<Self> {
let mut loader = ConfigLoader::builder();
for path in paths {
if path.as_ref().exists() {
loader = loader.add_source(File::from(path.as_ref()));
break; }
}
loader = loader.add_source(config::File::from_str(
include_str!("../../config/default.toml"),
config::FileFormat::Toml,
));
let config: ArfConfig = loader.build()?.try_deserialize()?;
Ok(Self { inner: config })
}
pub fn default_config() -> Self {
Self {
inner: ArfConfig {
version: env!("CARGO_PKG_VERSION").to_string(),
runtime: RuntimeConfig {
max_concurrent_sessions: 10,
session_timeout_seconds: 3600,
worker_threads: num_cpus::get(),
},
engine: EngineConfig {
max_steps_per_session: 50,
step_timeout_seconds: 300,
validation_enabled: true,
cognitive_load_monitoring: true,
},
plugins: PluginConfig {
enabled: true,
plugin_directory: "./plugins".to_string(),
max_plugins: 100,
hot_reload: false,
},
logging: LoggingConfig {
level: "INFO".to_string(),
format: "json".to_string(),
file_output: Some("./logs/arf.log".to_string()),
},
},
}
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let content = serde_json::to_string_pretty(&self.inner).map_err(|e| {
crate::error::Error::config(format!("Failed to serialize config: {}", e))
})?;
std::fs::write(path, content)?;
Ok(())
}
pub fn runtime(&self) -> &RuntimeConfig {
&self.inner.runtime
}
pub fn engine(&self) -> &EngineConfig {
&self.inner.engine
}
pub fn plugins(&self) -> &PluginConfig {
&self.inner.plugins
}
pub fn logging(&self) -> &LoggingConfig {
&self.inner.logging
}
}
impl Default for Config {
fn default() -> Self {
Self::default_config()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn test_default_config() {
let config = Config::default_config();
assert_eq!(config.runtime().max_concurrent_sessions, 10);
assert_eq!(config.engine().max_steps_per_session, 50);
assert!(config.plugins().enabled);
}
#[test]
fn test_config_save_load() {
let config = Config::default_config();
let temp_file = NamedTempFile::new().unwrap();
config.save_to_file(temp_file.path()).unwrap();
let loaded_config = Config::load_from_file(temp_file.path()).unwrap();
assert_eq!(
config.runtime().max_concurrent_sessions,
loaded_config.runtime().max_concurrent_sessions
);
}
}