Files
p/p/src/config.rs
Valentin Haudiquet 9bf111b417
Some checks failed
CI / Check, test, lint (push) Failing after 51s
ci: add ci tests, checks (clippy, fmt)
2026-05-20 11:06:21 +02:00

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());
}
}