vespertide-loader 0.3.0

Loads migrations and models from filesystem
Documentation
use std::fs;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use vespertide_config::VespertideConfig;

/// Read and parse a vespertide.json file that is known to exist.
fn read_config(path: &Path) -> Result<VespertideConfig> {
    let content = fs::read_to_string(path).context("read vespertide.json")?;
    let config: VespertideConfig =
        serde_json::from_str(&content).context("parse vespertide.json")?;
    Ok(config)
}

/// Load vespertide.json config from current directory.
pub fn load_config() -> Result<VespertideConfig> {
    let path = PathBuf::from("vespertide.json");
    if !path.exists() {
        anyhow::bail!("vespertide.json not found. Run 'vespertide init' first.");
    }

    read_config(&path)
}

/// Load config from a specific path.
pub fn load_config_from_path(path: impl AsRef<Path>) -> Result<VespertideConfig> {
    let path = path.as_ref();
    if !path.exists() {
        anyhow::bail!("vespertide.json not found at: {}", path.display());
    }

    read_config(path)
}

/// Load config from project root, with fallback to defaults.
pub fn load_config_or_default(project_root: Option<PathBuf>) -> Result<VespertideConfig> {
    let config_path = if let Some(root) = project_root {
        root.join("vespertide.json")
    } else {
        PathBuf::from("vespertide.json")
    };

    if config_path.exists() {
        load_config_from_path(config_path)
    } else {
        Ok(VespertideConfig::default())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_support::{CwdGuard, write_default_config};
    use serial_test::serial;
    use tempfile::tempdir;

    #[test]
    #[serial]
    fn test_load_config_from_path_success() {
        let tmp = tempdir().unwrap();
        let config_path = tmp.path().join("vespertide.json");
        write_default_config(&config_path);

        let result = load_config_from_path(config_path);
        assert!(result.is_ok());
        let config = result.unwrap();
        assert_eq!(config.models_dir, PathBuf::from("models"));
    }

    #[test]
    #[serial]
    fn test_load_config_from_path_not_found() {
        let tmp = tempdir().unwrap();
        let config_path = tmp.path().join("nonexistent.json");

        let result = load_config_from_path(config_path.clone());
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(err_msg.contains("vespertide.json not found at:"));
        assert!(err_msg.contains(&config_path.display().to_string()));
    }

    #[test]
    #[serial]
    fn test_load_config_or_default_with_root() {
        let tmp = tempdir().unwrap();
        let config_path = tmp.path().join("vespertide.json");
        write_default_config(&config_path);

        let result = load_config_or_default(Some(tmp.path().to_path_buf()));
        assert!(result.is_ok());
        let config = result.unwrap();
        assert_eq!(config.models_dir, PathBuf::from("models"));
    }

    #[test]
    #[serial]
    fn test_load_config_or_default_without_root() {
        let tmp = tempdir().unwrap();
        let _guard = CwdGuard::new(tmp.path());
        let config_path = PathBuf::from("vespertide.json");
        write_default_config(&config_path);

        let result = load_config_or_default(None);
        assert!(result.is_ok());
        let config = result.unwrap();
        assert_eq!(config.models_dir, PathBuf::from("models"));
    }

    #[test]
    #[serial]
    fn test_load_config_or_default_fallback_to_default() {
        let tmp = tempdir().unwrap();
        let _guard = CwdGuard::new(tmp.path());

        let result = load_config_or_default(None);
        assert!(result.is_ok());
        let config = result.unwrap();
        assert_eq!(config.models_dir, PathBuf::from("models"));
    }

    #[test]
    #[serial]
    fn test_load_config_success() {
        let tmp = tempdir().unwrap();
        let _guard = CwdGuard::new(tmp.path());
        let config_path = PathBuf::from("vespertide.json");
        write_default_config(&config_path);

        let result = load_config();
        assert!(result.is_ok());
        let config = result.unwrap();
        assert_eq!(config.models_dir, PathBuf::from("models"));
    }

    #[test]
    #[serial]
    fn test_load_config_not_found() {
        let tmp = tempdir().unwrap();
        let _guard = CwdGuard::new(tmp.path());

        let result = load_config();
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(err_msg.contains("vespertide.json not found"));
    }
}