140 lines
4.8 KiB
Rust
140 lines
4.8 KiB
Rust
use anyhow::Result;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
// ── Config types ──────────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
|
pub struct Config {
|
|
pub default_worker: Option<String>,
|
|
#[serde(default)]
|
|
pub workers: Vec<WorkerConfig>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct WorkerConfig {
|
|
pub name: String,
|
|
pub connection: String,
|
|
}
|
|
|
|
// ── Path helpers ──────────────────────────────────────────────────────────────
|
|
|
|
pub fn config_path() -> Result<PathBuf> {
|
|
Ok(dirs::config_dir()
|
|
.ok_or_else(|| anyhow::anyhow!("could not determine config directory"))?
|
|
.join("p")
|
|
.join("config.yaml"))
|
|
}
|
|
|
|
// ── Load / save ───────────────────────────────────────────────────────────────
|
|
|
|
pub fn load() -> Result<Config> {
|
|
let path = config_path()?;
|
|
if !path.exists() {
|
|
return Ok(Config::default());
|
|
}
|
|
let content = std::fs::read_to_string(&path)
|
|
.map_err(|e| anyhow::anyhow!("failed to read {}: {}", path.display(), e))?;
|
|
serde_yaml::from_str(&content).map_err(|e| anyhow::anyhow!("failed to parse config: {}", e))
|
|
}
|
|
|
|
pub fn save(config: &Config) -> Result<()> {
|
|
let path = config_path()?;
|
|
std::fs::create_dir_all(path.parent().unwrap_or(Path::new(".")))?;
|
|
let content = serde_yaml::to_string(config)
|
|
.map_err(|e| anyhow::anyhow!("failed to serialise config: {}", e))?;
|
|
std::fs::write(&path, content)
|
|
.map_err(|e| anyhow::anyhow!("failed to write {}: {}", path.display(), e))
|
|
}
|
|
|
|
// ── Lookup helpers ────────────────────────────────────────────────────────────
|
|
|
|
impl Config {
|
|
pub fn get_worker(&self, name: &str) -> Option<&WorkerConfig> {
|
|
self.workers.iter().find(|w| w.name == name)
|
|
}
|
|
|
|
pub fn default_worker(&self) -> Option<&WorkerConfig> {
|
|
self.get_worker(self.default_worker.as_deref()?)
|
|
}
|
|
|
|
/// Resolve a worker by optional name, falling back to the default.
|
|
pub fn resolve_worker(&self, name: Option<&str>) -> Result<&WorkerConfig> {
|
|
match name {
|
|
Some(n) => self
|
|
.get_worker(n)
|
|
.ok_or_else(|| anyhow::anyhow!("unknown worker '{}'", n)),
|
|
None => self.default_worker().ok_or_else(|| {
|
|
anyhow::anyhow!(
|
|
"no default worker configured — run 'p register <connection>' first"
|
|
)
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn two_worker_config() -> Config {
|
|
Config {
|
|
default_worker: Some("beefy".to_string()),
|
|
workers: vec![
|
|
WorkerConfig {
|
|
name: "beefy".to_string(),
|
|
connection: "user@192.168.1.50".to_string(),
|
|
},
|
|
WorkerConfig {
|
|
name: "cloud".to_string(),
|
|
connection: "user@cloud.example.com".to_string(),
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn get_worker_found() {
|
|
assert!(two_worker_config().get_worker("beefy").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn get_worker_not_found() {
|
|
assert!(two_worker_config().get_worker("unknown").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn default_worker_returns_correct_name() {
|
|
let cfg = two_worker_config();
|
|
assert_eq!(cfg.default_worker().unwrap().name, "beefy");
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_worker_by_name() {
|
|
let cfg = two_worker_config();
|
|
assert_eq!(cfg.resolve_worker(Some("cloud")).unwrap().name, "cloud");
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_worker_falls_back_to_default() {
|
|
let cfg = two_worker_config();
|
|
assert_eq!(cfg.resolve_worker(None).unwrap().name, "beefy");
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_worker_unknown_name_errors() {
|
|
assert!(two_worker_config().resolve_worker(Some("unknown")).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn resolve_worker_no_default_errors() {
|
|
let cfg = Config {
|
|
default_worker: None,
|
|
workers: vec![],
|
|
};
|
|
assert!(cfg.resolve_worker(None).is_err());
|
|
}
|
|
}
|