migrate-guard 0.1.1

Detect and auto-renumber colliding sqlx migration version numbers across git branches.
Documentation
//! `migrate-guard.toml` parsing.

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 {
    /// Load `migrate-guard.toml` from `root`.
    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"); // default
        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());
    }
}