#![forbid(unsafe_code)]
use std::path::{Path, PathBuf};
pub const NYM_PROXY_ENV: &str = "ZINGO_NYM_PROXY";
pub const NYM_PROXY_BINARY_NAME: &str = "nym-proxy";
#[derive(Debug, Default)]
pub struct SpawnHints<'a> {
pub explicit: Option<&'a str>,
pub bundled_dir: Option<PathBuf>,
}
pub fn resolve_proxy_path(hints: &SpawnHints<'_>) -> String {
choose_proxy_path(
hints.explicit,
std::env::var(NYM_PROXY_ENV).ok(),
bundled_proxy_path(hints.bundled_dir.as_deref()),
)
}
pub fn executable_sibling_dir() -> Option<PathBuf> {
Some(std::env::current_exe().ok()?.parent()?.to_path_buf())
}
fn choose_proxy_path(
explicit: Option<&str>,
env_value: Option<String>,
bundled: Option<String>,
) -> String {
if let Some(path) = explicit.filter(|path| !path.is_empty()) {
return path.to_string();
}
if let Some(path) = env_value.filter(|path| !path.is_empty()) {
return path;
}
if let Some(bundled) = bundled {
return bundled;
}
NYM_PROXY_BINARY_NAME.to_string()
}
fn bundled_proxy_path(bundled_dir: Option<&Path>) -> Option<String> {
let candidate = bundled_dir?.join(format!(
"{NYM_PROXY_BINARY_NAME}{}",
std::env::consts::EXE_SUFFIX
));
candidate
.is_file()
.then(|| candidate.to_string_lossy().into_owned())
}
#[cfg(test)]
mod precedence {
use super::*;
#[test]
fn explicit_wins_over_everything() {
let chosen = choose_proxy_path(
Some("/explicit"),
Some("/from-env".to_string()),
Some("/bundled".to_string()),
);
assert_eq!(chosen, "/explicit");
}
#[test]
fn empty_explicit_counts_as_absent() {
let chosen = choose_proxy_path(Some(""), Some("/from-env".to_string()), None);
assert_eq!(chosen, "/from-env");
}
#[test]
fn env_wins_over_bundled() {
let chosen = choose_proxy_path(
None,
Some("/from-env".to_string()),
Some("/bundled".to_string()),
);
assert_eq!(chosen, "/from-env");
}
#[test]
fn empty_env_counts_as_absent() {
let chosen = choose_proxy_path(None, Some(String::new()), Some("/bundled".to_string()));
assert_eq!(chosen, "/bundled");
}
#[test]
fn bundled_wins_over_bare_name() {
let chosen = choose_proxy_path(None, None, Some("/bundled".to_string()));
assert_eq!(chosen, "/bundled");
}
#[test]
fn bare_name_is_the_final_fallback() {
assert_eq!(choose_proxy_path(None, None, None), NYM_PROXY_BINARY_NAME);
}
#[test]
fn bundled_lookup_requires_the_file_to_exist() {
let dir = tempfile::tempdir().expect("a scratch directory");
assert_eq!(bundled_proxy_path(Some(dir.path())), None);
let name = format!("{NYM_PROXY_BINARY_NAME}{}", std::env::consts::EXE_SUFFIX);
std::fs::write(dir.path().join(&name), b"").expect("touch the candidate");
let found = bundled_proxy_path(Some(dir.path())).expect("the candidate is found");
assert!(found.ends_with(&name));
}
#[test]
fn no_bundled_dir_hint_means_no_bundled_candidate() {
assert_eq!(bundled_proxy_path(None), None);
}
}