auberge 0.8.5

CLI tool for managing self-hosted infrastructure with Ansible
use crate::user_config::UserConfig;
use eyre::{Context, Result};
use serde::Deserialize;
use std::path::PathBuf;

#[derive(Debug, Clone, Deserialize)]
pub struct Config {
    #[serde(default)]
    pub domain: String,
    #[serde(default = "default_ttl")]
    pub default_ttl: u32,
}

fn default_ttl() -> u32 {
    300
}

impl Config {
    pub fn load() -> Result<Self> {
        let config_path = UserConfig::path()?;
        let contents = std::fs::read_to_string(&config_path).wrap_err_with(|| {
            format!(
                "Config not found at {}. Run `auberge config init` first.",
                config_path.display()
            )
        })?;
        Self::parse(&contents)
    }

    fn parse(contents: &str) -> Result<Self> {
        let mut config: Self = toml::from_str(contents).wrap_err("Failed to parse config.toml")?;
        if config.domain.trim().is_empty() {
            eyre::bail!("'domain' is required in config.toml but is missing or empty");
        }
        config.domain = config.domain.trim().to_string();
        Ok(config)
    }

    pub fn config_dir() -> Result<PathBuf> {
        dirs::config_dir()
            .map(|p| p.join("auberge"))
            .ok_or_else(|| eyre::eyre!("Could not determine XDG config directory"))
    }

    pub fn data_dir() -> Result<PathBuf> {
        dirs::data_dir()
            .map(|p| p.join("auberge"))
            .ok_or_else(|| eyre::eyre!("Could not determine XDG data directory"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_ttl() {
        let config = Config::parse(r#"domain = "example.com""#).unwrap();
        assert_eq!(config.default_ttl, 300);
    }

    #[test]
    fn test_missing_domain_fails() {
        let err = Config::parse("default_ttl = 600").unwrap_err();
        assert!(err.to_string().contains("domain"));
    }

    #[test]
    fn test_empty_domain_fails() {
        let err = Config::parse(r#"domain = """#).unwrap_err();
        assert!(err.to_string().contains("domain"));
    }

    #[test]
    fn test_whitespace_domain_fails() {
        let err = Config::parse(r#"domain = "  ""#).unwrap_err();
        assert!(err.to_string().contains("domain"));
    }

    #[test]
    fn test_domain_is_trimmed() {
        let config = Config::parse(r#"domain = " example.com ""#).unwrap();
        assert_eq!(config.domain, "example.com");
    }
}