use std::path::{Path, PathBuf};
pub const DEFAULT_DAEMON_PORT: u16 = 60887;
pub const DEFAULT_BIND_HOST: &str = "127.0.0.1";
const BOOTSTRAP_RELATIVE_PATH: &str = ".gobby/bootstrap.yaml";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DaemonEndpoint {
pub host: String,
pub port: u16,
}
impl Default for DaemonEndpoint {
fn default() -> Self {
Self {
host: DEFAULT_BIND_HOST.to_string(),
port: DEFAULT_DAEMON_PORT,
}
}
}
pub fn bootstrap_path() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(BOOTSTRAP_RELATIVE_PATH))
}
pub fn read_daemon_endpoint() -> DaemonEndpoint {
match bootstrap_path() {
Some(path) => read_daemon_endpoint_at(&path),
None => DaemonEndpoint::default(),
}
}
pub fn read_daemon_endpoint_at(path: &Path) -> DaemonEndpoint {
let Ok(contents) = std::fs::read_to_string(path) else {
return DaemonEndpoint::default();
};
let Ok(yaml) = serde_yaml::from_str::<serde_yaml::Value>(&contents) else {
return DaemonEndpoint::default();
};
let port = yaml
.get("daemon_port")
.and_then(|v| v.as_u64())
.and_then(|n| u16::try_from(n).ok())
.unwrap_or(DEFAULT_DAEMON_PORT);
let host = yaml
.get("bind_host")
.and_then(|v| v.as_str())
.map(str::to_owned)
.unwrap_or_else(|| DEFAULT_BIND_HOST.to_string());
DaemonEndpoint { host, port }
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn missing_file_returns_defaults() {
let dir = tempdir().unwrap();
let path = dir.path().join("does-not-exist.yaml");
assert_eq!(read_daemon_endpoint_at(&path), DaemonEndpoint::default());
}
#[test]
fn malformed_yaml_returns_defaults() {
let dir = tempdir().unwrap();
let path = dir.path().join("bootstrap.yaml");
fs::write(&path, ": : not valid yaml ::\n\t").unwrap();
assert_eq!(read_daemon_endpoint_at(&path), DaemonEndpoint::default());
}
#[test]
fn empty_file_returns_defaults() {
let dir = tempdir().unwrap();
let path = dir.path().join("bootstrap.yaml");
fs::write(&path, "").unwrap();
assert_eq!(read_daemon_endpoint_at(&path), DaemonEndpoint::default());
}
#[test]
fn missing_fields_return_defaults() {
let dir = tempdir().unwrap();
let path = dir.path().join("bootstrap.yaml");
fs::write(&path, "other_field: value\n").unwrap();
assert_eq!(read_daemon_endpoint_at(&path), DaemonEndpoint::default());
}
#[test]
fn reads_custom_port() {
let dir = tempdir().unwrap();
let path = dir.path().join("bootstrap.yaml");
fs::write(&path, "daemon_port: 61234\n").unwrap();
let ep = read_daemon_endpoint_at(&path);
assert_eq!(ep.port, 61234);
assert_eq!(ep.host, DEFAULT_BIND_HOST);
}
#[test]
fn reads_custom_host_and_port() {
let dir = tempdir().unwrap();
let path = dir.path().join("bootstrap.yaml");
fs::write(&path, "daemon_port: 60887\nbind_host: 0.0.0.0\n").unwrap();
let ep = read_daemon_endpoint_at(&path);
assert_eq!(ep.port, 60887);
assert_eq!(ep.host, "0.0.0.0");
}
#[test]
fn out_of_range_port_falls_back() {
let dir = tempdir().unwrap();
let path = dir.path().join("bootstrap.yaml");
fs::write(&path, "daemon_port: 70000\n").unwrap();
assert_eq!(read_daemon_endpoint_at(&path).port, DEFAULT_DAEMON_PORT);
}
}