use std::collections::HashSet;
use std::sync::Mutex;
use once_cell::sync::Lazy;
static WARNED: Lazy<Mutex<HashSet<&'static str>>> = Lazy::new(|| Mutex::new(HashSet::new()));
pub fn read_env_with_legacy(new_name: &'static str, legacy_name: &'static str) -> Option<String> {
if let Ok(v) = std::env::var(new_name) {
if !v.is_empty() {
return Some(v);
}
}
match std::env::var(legacy_name) {
Ok(v) if !v.is_empty() => {
warn_legacy_once(legacy_name, new_name);
Some(v)
}
_ => None,
}
}
pub fn warn_legacy_once(legacy_name: &'static str, new_name: &'static str) {
let mut warned = match WARNED.lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
if warned.insert(legacy_name) {
eprintln!(
"[socket-patch] warning: env var `{legacy_name}` is deprecated; \
use `{new_name}` instead. The legacy name will be removed in a \
future major release."
);
}
}
pub const LEGACY_ENV_RENAMES: &[(&str, &str)] = &[
("SOCKET_PROXY_URL", "SOCKET_PATCH_PROXY_URL"),
("SOCKET_DEBUG", "SOCKET_PATCH_DEBUG"),
(
"SOCKET_TELEMETRY_DISABLED",
"SOCKET_PATCH_TELEMETRY_DISABLED",
),
];
pub fn promote_legacy_env_vars() {
for (new_name, legacy_name) in LEGACY_ENV_RENAMES {
let new_already_set = std::env::var(new_name)
.ok()
.filter(|v| !v.is_empty())
.is_some();
if new_already_set {
continue;
}
if let Ok(value) = std::env::var(legacy_name) {
if !value.is_empty() {
warn_legacy_once(legacy_name, new_name);
std::env::set_var(new_name, value);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn warn_legacy_once_fires_only_once_per_name() {
let name = "SOCKET_TEST_LEGACY_ONCE_PATCH";
let new = "SOCKET_TEST_LEGACY_ONCE";
warn_legacy_once(name, new);
warn_legacy_once(name, new);
let mut warned = WARNED.lock().unwrap();
assert!(warned.contains(name));
assert!(
!warned.insert(name),
"name should already be recorded, so a second warning is suppressed"
);
}
#[test]
fn read_env_prefers_new_var_over_legacy() {
const NEW: &str = "SOCKET_TEST_READ_PREFERS_NEW";
const LEGACY: &str = "SOCKET_TEST_READ_PREFERS_NEW_PATCH";
std::env::set_var(NEW, "new-value");
std::env::set_var(LEGACY, "legacy-value");
assert_eq!(
read_env_with_legacy(NEW, LEGACY),
Some("new-value".to_string())
);
std::env::remove_var(NEW);
std::env::remove_var(LEGACY);
}
#[test]
fn read_env_falls_back_to_legacy_when_new_unset() {
const NEW: &str = "SOCKET_TEST_READ_FALLBACK_NEW";
const LEGACY: &str = "SOCKET_TEST_READ_FALLBACK_NEW_PATCH";
std::env::remove_var(NEW);
std::env::set_var(LEGACY, "legacy-value");
assert_eq!(
read_env_with_legacy(NEW, LEGACY),
Some("legacy-value".to_string())
);
std::env::remove_var(LEGACY);
}
#[test]
fn read_env_empty_new_falls_back_to_legacy() {
const NEW: &str = "SOCKET_TEST_READ_EMPTY_NEW";
const LEGACY: &str = "SOCKET_TEST_READ_EMPTY_NEW_PATCH";
std::env::set_var(NEW, "");
std::env::set_var(LEGACY, "legacy-value");
assert_eq!(
read_env_with_legacy(NEW, LEGACY),
Some("legacy-value".to_string())
);
std::env::remove_var(NEW);
std::env::remove_var(LEGACY);
}
#[test]
fn read_env_none_when_neither_set() {
const NEW: &str = "SOCKET_TEST_READ_NONE_NEW";
const LEGACY: &str = "SOCKET_TEST_READ_NONE_NEW_PATCH";
std::env::remove_var(NEW);
std::env::remove_var(LEGACY);
assert_eq!(read_env_with_legacy(NEW, LEGACY), None);
}
#[test]
fn read_env_none_when_both_empty() {
const NEW: &str = "SOCKET_TEST_READ_BOTH_EMPTY_NEW";
const LEGACY: &str = "SOCKET_TEST_READ_BOTH_EMPTY_NEW_PATCH";
std::env::set_var(NEW, "");
std::env::set_var(LEGACY, "");
assert_eq!(read_env_with_legacy(NEW, LEGACY), None);
std::env::remove_var(NEW);
std::env::remove_var(LEGACY);
}
}