running_process/broker/
broker_http_port.rs1use std::env;
10use std::net::{IpAddr, Ipv4Addr};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum BrokerHttpPort {
20 Static {
22 port: u16,
24 },
25 Dynamic,
27 StaticOrFallback {
29 preferred: u16,
31 },
32}
33
34pub const PORT_OVERRIDE_ENV: &str = "RUNNING_PROCESS_BROKER_HTTP_PORT";
38
39pub const BIND_OVERRIDE_ENV: &str = "RUNNING_PROCESS_BROKER_HTTP_BIND";
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct ResolvedHttpBind {
46 pub port: BrokerHttpPort,
48 pub addr: IpAddr,
50}
51
52impl BrokerHttpPort {
53 pub fn resolve(config: BrokerHttpPort) -> ResolvedHttpBind {
65 let port = match parse_port_env() {
66 Some(p) => BrokerHttpPort::Static { port: p },
67 None => config,
68 };
69 let addr = parse_bind_env().unwrap_or(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
70 ResolvedHttpBind { port, addr }
71 }
72}
73
74fn parse_port_env() -> Option<u16> {
75 let raw = env::var(PORT_OVERRIDE_ENV).ok()?;
76 let trimmed = raw.trim();
77 if trimmed.is_empty() {
78 return None;
79 }
80 trimmed.parse::<u16>().ok()
81}
82
83fn parse_bind_env() -> Option<IpAddr> {
84 let raw = env::var(BIND_OVERRIDE_ENV).ok()?;
85 let trimmed = raw.trim();
86 if trimmed.is_empty() {
87 return None;
88 }
89 trimmed.parse::<IpAddr>().ok()
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use std::sync::Mutex;
96
97 static ENV_LOCK: Mutex<()> = Mutex::new(());
101
102 fn with_env<F: FnOnce()>(port: Option<&str>, bind: Option<&str>, f: F) {
103 let _g = ENV_LOCK.lock().expect("env mutex poisoned");
104 let prev_port = env::var(PORT_OVERRIDE_ENV).ok();
106 let prev_bind = env::var(BIND_OVERRIDE_ENV).ok();
107 match port {
108 Some(p) => env::set_var(PORT_OVERRIDE_ENV, p),
109 None => env::remove_var(PORT_OVERRIDE_ENV),
110 }
111 match bind {
112 Some(b) => env::set_var(BIND_OVERRIDE_ENV, b),
113 None => env::remove_var(BIND_OVERRIDE_ENV),
114 }
115 f();
116 match prev_port {
118 Some(p) => env::set_var(PORT_OVERRIDE_ENV, p),
119 None => env::remove_var(PORT_OVERRIDE_ENV),
120 }
121 match prev_bind {
122 Some(b) => env::set_var(BIND_OVERRIDE_ENV, b),
123 None => env::remove_var(BIND_OVERRIDE_ENV),
124 }
125 }
126
127 #[test]
128 fn no_env_returns_config_and_loopback_default() {
129 with_env(None, None, || {
130 let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
131 assert_eq!(r.port, BrokerHttpPort::Dynamic);
132 assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
133 });
134 }
135
136 #[test]
137 fn port_env_set_overrides_to_static() {
138 with_env(Some("8080"), None, || {
139 let r = BrokerHttpPort::resolve(BrokerHttpPort::StaticOrFallback {
140 preferred: 12_345,
141 });
142 assert_eq!(r.port, BrokerHttpPort::Static { port: 8080 });
143 });
144 }
145
146 #[test]
147 fn bind_env_set_overrides_addr() {
148 with_env(None, Some("0.0.0.0"), || {
149 let r = BrokerHttpPort::resolve(BrokerHttpPort::Static { port: 4242 });
150 assert_eq!(r.port, BrokerHttpPort::Static { port: 4242 });
151 assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
152 });
153 }
154
155 #[test]
156 fn invalid_port_env_falls_back_to_config() {
157 with_env(Some("not-a-port"), None, || {
158 let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
159 assert_eq!(r.port, BrokerHttpPort::Dynamic);
160 });
161 }
162
163 #[test]
164 fn empty_port_env_falls_back_to_config() {
165 with_env(Some(""), None, || {
166 let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
167 assert_eq!(r.port, BrokerHttpPort::Dynamic);
168 });
169 }
170
171 #[test]
172 fn invalid_bind_env_falls_back_to_loopback() {
173 with_env(None, Some("not-an-ip"), || {
174 let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
175 assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
176 });
177 }
178
179 #[test]
180 fn both_env_overrides_compose() {
181 with_env(Some("9999"), Some("0.0.0.0"), || {
182 let r = BrokerHttpPort::resolve(BrokerHttpPort::Dynamic);
183 assert_eq!(r.port, BrokerHttpPort::Static { port: 9999 });
184 assert_eq!(r.addr, IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
185 });
186 }
187}