xbp 10.8.0

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
//! configuration management module
//!
//! handles ssh configuration and yaml config file management
//! provides loading and saving of configuration files
//! supports home directory based config storage

use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SshConfig {
    pub password: Option<String>,
    pub username: Option<String>,
    pub host: Option<String>,
    pub project_dir: Option<String>,
}

impl SshConfig {
    pub fn new() -> Self {
        SshConfig {
            password: None,
            username: None,
            host: None,
            project_dir: None,
        }
    }

    pub fn load() -> Result<Self, String> {
        let config_path = get_config_path();
        if !config_path.exists() {
            return Ok(SshConfig::new());
        }

        let content = fs::read_to_string(&config_path)
            .map_err(|e| format!("Failed to read config file: {}", e))?;
        serde_yaml::from_str(&content).map_err(|e| format!("Failed to parse config file: {}", e))
    }

    pub fn save(&self) -> Result<(), String> {
        let config_path = get_config_path();
        let config_dir = config_path.parent().ok_or("Invalid config path")?;
        fs::create_dir_all(config_dir)
            .map_err(|e| format!("Failed to create config directory: {}", e))?;

        let content = serde_yaml::to_string(self)
            .map_err(|e| format!("Failed to serialize config: {}", e))?;
        fs::write(&config_path, content).map_err(|e| format!("Failed to write config file: {}", e))
    }
}

fn get_config_path() -> PathBuf {
    dirs::home_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join(".xbp")
        .join("config.yaml")
}

const DEFAULT_API_XBP_URL: &str = "https://api.xbp.app";

/// Simple API configuration for the XBP version endpoints.
#[derive(Debug, Clone)]
pub struct ApiConfig {
    base_url: String,
}

impl ApiConfig {
    /// Load the API configuration from API_XBP_URL, falling back to the default.
    pub fn load() -> Self {
        let raw_url = env::var("API_XBP_URL").unwrap_or_else(|_| DEFAULT_API_XBP_URL.to_string());
        let base_url = Self::normalize_base_url(&raw_url);
        ApiConfig { base_url }
    }

    /// Return the normalized base URL that downstream callers should use.
    pub fn base_url(&self) -> &str {
        &self.base_url
    }

    /// Build the version query endpoint.
    pub fn version_endpoint(&self, project_name: &str) -> String {
        format!("{}/version?project_name={}", self.base_url, project_name)
    }

    /// Build the endpoint that increments a version.
    pub fn increment_endpoint(&self) -> String {
        format!("{}/version/increment", self.base_url)
    }

    fn normalize_base_url(raw: &str) -> String {
        let trimmed = raw.trim();
        if trimmed.is_empty() {
            return DEFAULT_API_XBP_URL.to_string();
        }

        let trimmed = trimmed.trim_end_matches('/');
        if trimmed.is_empty() {
            return DEFAULT_API_XBP_URL.to_string();
        }

        trimmed.to_string()
    }
}