use crate::error::{GuardError, Result};
use serde::Deserialize;
use std::path::{Path, PathBuf};
#[derive(Debug, Deserialize)]
pub struct Config {
#[serde(default = "default_git")]
pub git: String,
#[serde(default = "default_base")]
pub base_ref: String,
#[serde(default = "default_globs")]
pub reference_globs: Vec<String>,
#[serde(rename = "dir", default)]
pub dirs: Vec<DirSpec>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct DirSpec {
pub role: String,
pub path: PathBuf,
}
fn default_git() -> String {
"git".into()
}
fn default_base() -> String {
"main".into()
}
fn default_globs() -> Vec<String> {
vec!["src/**/*.rs".into()]
}
impl Config {
pub fn load(root: &Path) -> Result<Config> {
let path = root.join("migrate-guard.toml");
let text = std::fs::read_to_string(&path)
.map_err(|e| GuardError::Config(format!("reading {}: {e}", path.display())))?;
let cfg: Config = toml::from_str(&text)
.map_err(|e| GuardError::Config(format!("parsing {}: {e}", path.display())))?;
if cfg.dirs.is_empty() {
return Err(GuardError::Config("no [[dir]] entries".into()));
}
Ok(cfg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_full_config() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("migrate-guard.toml"),
r#"
base_ref = "trunk"
[[dir]]
role = "platform"
path = "backend/migrations"
[[dir]]
role = "tenant"
path = "crates/fastyoke-kernel/migrations_tenant"
"#,
)
.unwrap();
let cfg = Config::load(tmp.path()).unwrap();
assert_eq!(cfg.base_ref, "trunk");
assert_eq!(cfg.git, "git"); assert_eq!(cfg.dirs.len(), 2);
assert_eq!(cfg.dirs[0].role, "platform");
}
#[test]
fn rejects_no_dirs() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("migrate-guard.toml"),
"base_ref = \"main\"\n",
)
.unwrap();
assert!(Config::load(tmp.path()).is_err());
}
}