use std::net::SocketAddr;
use std::path::{Path, PathBuf};
pub const TRUSTY_MEMORY_DEFAULT_ADDR: &str = "127.0.0.1:3038";
pub const TRUSTY_SEARCH_DEFAULT_ADDR: &str = "127.0.0.1:7878";
const TRUSTY_MEMORY_DATA_DIR: &str = ".trusty-memory";
const TRUSTY_SEARCH_DATA_DIR: &str = ".trusty-search";
const HTTP_ADDR_FILE: &str = "http_addr";
#[derive(Debug, Clone)]
pub struct TrustyAddrs {
pub memory: SocketAddr,
pub search: SocketAddr,
}
pub async fn discover_addr(
data_dir: &Path,
default_addr: SocketAddr,
env_override: Option<&str>,
) -> SocketAddr {
if let Some(raw) = env_override
&& let Ok(addr) = raw.trim().parse::<SocketAddr>()
{
return addr;
}
let port_file = data_dir.join(HTTP_ADDR_FILE);
if let Ok(contents) = tokio::fs::read_to_string(&port_file).await
&& let Ok(addr) = contents.trim().parse::<SocketAddr>()
{
return addr;
}
default_addr
}
pub async fn discover_all(home: &Path) -> TrustyAddrs {
let memory_dir = home.join(TRUSTY_MEMORY_DATA_DIR);
let search_dir = home.join(TRUSTY_SEARCH_DATA_DIR);
let memory_default: SocketAddr = TRUSTY_MEMORY_DEFAULT_ADDR
.parse()
.expect("static default is valid");
let search_default: SocketAddr = TRUSTY_SEARCH_DEFAULT_ADDR
.parse()
.expect("static default is valid");
let memory_env = std::env::var("TRUSTY_MEMORY_ADDR").ok();
let search_env = std::env::var("TRUSTY_SEARCH_ADDR").ok();
let (memory, search) = tokio::join!(
discover_addr(&memory_dir, memory_default, memory_env.as_deref()),
discover_addr(&search_dir, search_default, search_env.as_deref()),
);
TrustyAddrs { memory, search }
}
#[allow(dead_code)] pub fn addr_file(data_dir: &Path) -> PathBuf {
data_dir.join(HTTP_ADDR_FILE)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::TempDir;
fn write_addr_file(dir: &TempDir, addr: &str) {
let path = dir.path().join(HTTP_ADDR_FILE);
let mut f = std::fs::File::create(path).unwrap();
write!(f, "{addr}").unwrap();
}
#[tokio::test]
async fn returns_addr_from_file() {
let dir = TempDir::new().unwrap();
write_addr_file(&dir, "127.0.0.1:9999");
let default: SocketAddr = "127.0.0.1:3038".parse().unwrap();
let addr = discover_addr(dir.path(), default, None).await;
assert_eq!(addr, "127.0.0.1:9999".parse::<SocketAddr>().unwrap());
}
#[tokio::test]
async fn falls_back_to_default_when_file_absent() {
let dir = TempDir::new().unwrap();
let default: SocketAddr = "127.0.0.1:3038".parse().unwrap();
let addr = discover_addr(dir.path(), default, None).await;
assert_eq!(addr, default);
}
#[tokio::test]
async fn falls_back_to_default_when_file_malformed() {
let dir = TempDir::new().unwrap();
write_addr_file(&dir, "not-an-address");
let default: SocketAddr = "127.0.0.1:3038".parse().unwrap();
let addr = discover_addr(dir.path(), default, None).await;
assert_eq!(addr, default);
}
#[tokio::test]
async fn env_override_wins_over_file() {
let dir = TempDir::new().unwrap();
write_addr_file(&dir, "127.0.0.1:9999");
let default: SocketAddr = "127.0.0.1:3038".parse().unwrap();
let addr = discover_addr(dir.path(), default, Some("127.0.0.1:5555")).await;
assert_eq!(addr, "127.0.0.1:5555".parse::<SocketAddr>().unwrap());
}
#[tokio::test]
async fn malformed_env_override_falls_through_to_file() {
let dir = TempDir::new().unwrap();
write_addr_file(&dir, "127.0.0.1:9999");
let default: SocketAddr = "127.0.0.1:3038".parse().unwrap();
let addr = discover_addr(dir.path(), default, Some("not-valid")).await;
assert_eq!(addr, "127.0.0.1:9999".parse::<SocketAddr>().unwrap());
}
#[tokio::test]
async fn discover_all_with_files() {
let mem_dir = TempDir::new().unwrap();
let srch_dir = TempDir::new().unwrap();
write_addr_file(&mem_dir, "127.0.0.1:4001");
write_addr_file(&srch_dir, "127.0.0.1:4002");
let mem_default: SocketAddr = TRUSTY_MEMORY_DEFAULT_ADDR.parse().unwrap();
let srch_default: SocketAddr = TRUSTY_SEARCH_DEFAULT_ADDR.parse().unwrap();
let (memory, search) = tokio::join!(
discover_addr(mem_dir.path(), mem_default, None),
discover_addr(srch_dir.path(), srch_default, None),
);
assert_eq!(memory, "127.0.0.1:4001".parse::<SocketAddr>().unwrap());
assert_eq!(search, "127.0.0.1:4002".parse::<SocketAddr>().unwrap());
}
#[test]
fn addr_file_path_ends_with_http_addr() {
let dir = PathBuf::from("/home/user/.trusty-memory");
let p = addr_file(&dir);
assert!(p.ends_with("http_addr"));
}
#[test]
fn constants_parse_as_socket_addrs() {
TRUSTY_MEMORY_DEFAULT_ADDR
.parse::<SocketAddr>()
.expect("TRUSTY_MEMORY_DEFAULT_ADDR must be a valid SocketAddr");
TRUSTY_SEARCH_DEFAULT_ADDR
.parse::<SocketAddr>()
.expect("TRUSTY_SEARCH_DEFAULT_ADDR must be a valid SocketAddr");
}
}