use crate::errors::WtpMcpError;
use serde::Deserialize;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct Config {
pub repo_root: Option<PathBuf>,
#[serde(default)]
pub wtp: WtpConfig,
#[serde(default)]
pub security: SecurityPolicy,
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct WtpConfig {
pub path: Option<PathBuf>,
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct SecurityPolicy {
pub allow_hooks: bool,
pub allow_branch_delete: bool,
}
pub struct CliArgs {
pub repo_root: Option<PathBuf>,
pub wtp_path: Option<PathBuf>,
}
impl Config {
pub fn load(path: Option<&Path>) -> Result<Config, WtpMcpError> {
match path {
Some(p) => {
let contents = std::fs::read_to_string(p).map_err(|e| WtpMcpError::ConfigRead {
path: p.to_path_buf(),
source: e,
})?;
toml::from_str(&contents).map_err(|e| WtpMcpError::ConfigParse {
path: p.to_path_buf(),
source: e,
})
}
None => Ok(Config::default()),
}
}
pub fn merge_cli(&mut self, cli: &CliArgs) {
if cli.repo_root.is_some() {
self.repo_root = cli.repo_root.clone();
}
if cli.wtp_path.is_some() {
self.wtp.path = cli.wtp_path.clone();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::{fixture, rstest};
#[fixture]
fn default_config() -> Config {
Config::default()
}
#[rstest]
fn test_default_values(default_config: Config) {
assert!(default_config.repo_root.is_none());
assert!(!default_config.security.allow_hooks);
assert!(!default_config.security.allow_branch_delete);
}
#[rstest]
#[case(Some("/tmp/repo"), None, Some("/tmp/repo"), None)]
#[case(None, Some("/usr/local/bin/wtp"), None, Some("/usr/local/bin/wtp"))]
fn test_merge_cli(
mut default_config: Config,
#[case] repo_root: Option<&str>,
#[case] wtp_path: Option<&str>,
#[case] expected_repo: Option<&str>,
#[case] expected_wtp: Option<&str>,
) {
let cli = CliArgs {
repo_root: repo_root.map(PathBuf::from),
wtp_path: wtp_path.map(PathBuf::from),
};
default_config.merge_cli(&cli);
assert_eq!(default_config.repo_root, expected_repo.map(PathBuf::from));
assert_eq!(default_config.wtp.path, expected_wtp.map(PathBuf::from));
}
#[rstest]
fn test_load_missing_file_returns_default() {
let config = Config::load(None).unwrap();
assert!(config.repo_root.is_none());
}
#[rstest]
fn test_deserialize_partial_toml() {
let toml_str = r#"
repo_root = "/my/repo"
[wtp]
path = "/usr/local/bin/wtp"
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.repo_root, Some(PathBuf::from("/my/repo")));
assert_eq!(config.wtp.path, Some(PathBuf::from("/usr/local/bin/wtp")));
}
}