use std::env;
use std::net::{IpAddr, Ipv4Addr};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrokerHttpPort {
Static {
port: u16,
},
Dynamic,
StaticOrFallback {
preferred: u16,
},
}
pub const PORT_OVERRIDE_ENV: &str = "RUNNING_PROCESS_BROKER_HTTP_PORT";
pub const BIND_OVERRIDE_ENV: &str = "RUNNING_PROCESS_BROKER_HTTP_BIND";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResolvedHttpBind {
pub port: BrokerHttpPort,
pub addr: IpAddr,
}
impl BrokerHttpPort {
pub fn resolve(config: BrokerHttpPort) -> ResolvedHttpBind {
let port = match parse_port_env() {
Some(p) => BrokerHttpPort::Static { port: p },
None => config,
};
let addr = parse_bind_env().unwrap_or(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
ResolvedHttpBind { port, addr }
}
}
fn parse_port_env() -> Option<u16> {
let raw = env::var(PORT_OVERRIDE_ENV).ok()?;
let trimmed = raw.trim();
if trimmed.is_empty() {
return None;
}
trimmed.parse::<u16>().ok()
}
fn parse_bind_env() -> Option<IpAddr> {
let raw = env::var(BIND_OVERRIDE_ENV).ok()?;
let trimmed = raw.trim();
if trimmed.is_empty() {
return None;
}
trimmed.parse::<IpAddr>().ok()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn with_env<F: FnOnce()>(port: Option<&str>, bind: Option<&str>, f: F) {
let _g = ENV_LOCK.lock().expect("env mutex poisoned");
let prev_port = env::var(PORT_OVERRIDE_ENV).ok();
let prev_bind = env::var(BIND_OVERRIDE_ENV).ok();
match port {
Some(p) => env::set_var(PORT_OVERRIDE_ENV, p),
None => env::remove_var(PORT_OVERRIDE_ENV),
}
match bind {
Some(b) => env::set_var(BIND_OVERRIDE_ENV, b),
None => env::remove_var(BIND_OVERRIDE_ENV),
}
f();
match prev_port {
Some(p) => env::set_var(PORT_OVERRIDE_ENV, p),
None => env::remove_var(PORT_OVERRIDE_ENV),
}
match prev_bind {
Some(b) => env::set_var(BIND_OVERRIDE_ENV, b),
None => env::remove_var(BIND_OVERRIDE_ENV),
}
}
#[test]
fn no_env_returns_config_and_loopback_default() {
with_env(None, None, || {
let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
assert_eq!(r.port, BrokerHttpPort::Dynamic);
assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
});
}
#[test]
fn port_env_set_overrides_to_static() {
with_env(Some("8080"), None, || {
let r = BrokerHttpPort::resolve(BrokerHttpPort::StaticOrFallback {
preferred: 12_345,
});
assert_eq!(r.port, BrokerHttpPort::Static { port: 8080 });
});
}
#[test]
fn bind_env_set_overrides_addr() {
with_env(None, Some("0.0.0.0"), || {
let r = BrokerHttpPort::resolve(BrokerHttpPort::Static { port: 4242 });
assert_eq!(r.port, BrokerHttpPort::Static { port: 4242 });
assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
});
}
#[test]
fn invalid_port_env_falls_back_to_config() {
with_env(Some("not-a-port"), None, || {
let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
assert_eq!(r.port, BrokerHttpPort::Dynamic);
});
}
#[test]
fn empty_port_env_falls_back_to_config() {
with_env(Some(""), None, || {
let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
assert_eq!(r.port, BrokerHttpPort::Dynamic);
});
}
#[test]
fn invalid_bind_env_falls_back_to_loopback() {
with_env(None, Some("not-an-ip"), || {
let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
});
}
#[test]
fn both_env_overrides_compose() {
with_env(Some("9999"), Some("0.0.0.0"), || {
let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
assert_eq!(r.port, BrokerHttpPort::Static { port: 9999 });
assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
});
}
}