Skip to content

Commit 9561502

Browse files
fix: reject port 0, extract shared addr resolution, reduce resolve_addr visibility
- Extract resolve_bind_addr into edgezero_core::addr so both the axum adapter and the CLI dev server share a single code path for resolving bind addresses from env vars and config. - Reject port 0 (random OS port) with a warning and fallback to 8787, matching the existing cli.rs validation. - Narrow resolve_addr to pub(crate) since it has no external consumers.
1 parent 38fa00d commit 9561502

4 files changed

Lines changed: 151 additions & 21 deletions

File tree

crates/edgezero-adapter-axum/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub mod cli;
2121
#[cfg(feature = "axum")]
2222
pub use context::AxumRequestContext;
2323
#[cfg(feature = "axum")]
24-
pub use dev_server::{resolve_addr, run_app, AxumDevServer, AxumDevServerConfig};
24+
pub use dev_server::{run_app, AxumDevServer, AxumDevServerConfig};
2525
#[cfg(feature = "axum")]
2626
pub use key_value_store::PersistentKvStore;
2727
#[cfg(feature = "axum")]

crates/edgezero-cli/src/dev_server.rs

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,9 @@ async fn dev_echo(Path(params): Path<EchoParams>) -> Text<String> {
8686
/// Resolve the dev server bind address from `EDGEZERO_HOST` / `EDGEZERO_PORT`
8787
/// environment variables, falling back to `127.0.0.1:8787`.
8888
fn resolve_dev_addr() -> SocketAddr {
89-
let default_host: std::net::IpAddr = [127, 0, 0, 1].into();
90-
let host = match std::env::var("EDGEZERO_HOST") {
91-
Ok(v) => v.parse().unwrap_or_else(|_| {
92-
eprintln!(
93-
"[edgezero] warning: EDGEZERO_HOST={v:?} is not a valid IP address, using default"
94-
);
95-
default_host
96-
}),
97-
Err(_) => default_host,
98-
};
99-
let port = match std::env::var("EDGEZERO_PORT") {
100-
Ok(v) => v.parse().unwrap_or_else(|_| {
101-
eprintln!(
102-
"[edgezero] warning: EDGEZERO_PORT={v:?} is not a valid port number, using default"
103-
);
104-
8787
105-
}),
106-
Err(_) => 8787,
107-
};
108-
SocketAddr::from((host, port))
89+
let env_host = std::env::var("EDGEZERO_HOST").ok();
90+
let env_port = std::env::var("EDGEZERO_PORT").ok();
91+
edgezero_core::addr::resolve_bind_addr(env_host.as_deref(), env_port.as_deref(), None, None)
10992
}
11093

11194
fn try_run_manifest_axum() -> Result<bool, String> {

crates/edgezero-core/src/addr.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//! Shared bind-address resolution for EdgeZero dev servers.
2+
//!
3+
//! Centralises the precedence logic (env vars > config > defaults) so that
4+
//! both the Axum adapter and the CLI dev server produce consistent results.
5+
6+
use std::net::{IpAddr, SocketAddr};
7+
8+
const DEFAULT_HOST: IpAddr = IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1));
9+
const DEFAULT_PORT: u16 = 8787;
10+
11+
/// Resolve a bind address from optional environment and config values.
12+
///
13+
/// Precedence (highest wins):
14+
/// 1. `env_host` / `env_port` (typically `EDGEZERO_HOST` / `EDGEZERO_PORT`)
15+
/// 2. `config_host` / `config_port` (from manifest or adapter config)
16+
/// 3. Defaults: `127.0.0.1:8787`
17+
///
18+
/// Invalid values produce a `log::warn!` and fall back to the default.
19+
/// Port 0 is rejected (random OS port is almost never intended).
20+
pub fn resolve_bind_addr(
21+
env_host: Option<&str>,
22+
env_port: Option<&str>,
23+
config_host: Option<&str>,
24+
config_port: Option<u16>,
25+
) -> SocketAddr {
26+
let host = resolve_host(env_host, config_host);
27+
let port = resolve_port(env_port, config_port);
28+
SocketAddr::from((host, port))
29+
}
30+
31+
fn resolve_host(env_host: Option<&str>, config_host: Option<&str>) -> IpAddr {
32+
if let Some(v) = env_host {
33+
return v.parse().unwrap_or_else(|_| {
34+
log::warn!("EDGEZERO_HOST={v:?} is not a valid IP address, using default");
35+
DEFAULT_HOST
36+
});
37+
}
38+
if let Some(h) = config_host {
39+
return h.parse().unwrap_or_else(|_| {
40+
log::warn!("configured host={h:?} is not a valid IP address, using default");
41+
DEFAULT_HOST
42+
});
43+
}
44+
DEFAULT_HOST
45+
}
46+
47+
fn resolve_port(env_port: Option<&str>, config_port: Option<u16>) -> u16 {
48+
let port = if let Some(v) = env_port {
49+
v.parse().unwrap_or_else(|_| {
50+
log::warn!("EDGEZERO_PORT={v:?} is not a valid port number, using default");
51+
DEFAULT_PORT
52+
})
53+
} else {
54+
config_port.unwrap_or(DEFAULT_PORT)
55+
};
56+
57+
if port == 0 {
58+
log::warn!("port 0 is not supported, using default {DEFAULT_PORT}");
59+
return DEFAULT_PORT;
60+
}
61+
62+
port
63+
}
64+
65+
#[cfg(test)]
66+
mod tests {
67+
use super::*;
68+
use std::net::Ipv4Addr;
69+
70+
#[test]
71+
fn defaults_when_nothing_provided() {
72+
let addr = resolve_bind_addr(None, None, None, None);
73+
assert_eq!(addr, SocketAddr::from(([127, 0, 0, 1], 8787)));
74+
}
75+
76+
#[test]
77+
fn config_overrides_defaults() {
78+
let addr = resolve_bind_addr(None, None, Some("0.0.0.0"), Some(3000));
79+
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
80+
assert_eq!(addr.port(), 3000);
81+
}
82+
83+
#[test]
84+
fn env_overrides_config() {
85+
let addr = resolve_bind_addr(Some("0.0.0.0"), Some("4000"), Some("127.0.0.1"), Some(3000));
86+
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
87+
assert_eq!(addr.port(), 4000);
88+
}
89+
90+
#[test]
91+
fn partial_env_override_host_only() {
92+
let addr = resolve_bind_addr(Some("0.0.0.0"), None, None, Some(5000));
93+
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
94+
assert_eq!(addr.port(), 5000);
95+
}
96+
97+
#[test]
98+
fn partial_env_override_port_only() {
99+
let addr = resolve_bind_addr(None, Some("9000"), Some("0.0.0.0"), None);
100+
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)));
101+
assert_eq!(addr.port(), 9000);
102+
}
103+
104+
#[test]
105+
fn invalid_env_host_falls_back_to_default() {
106+
let addr = resolve_bind_addr(Some("not-an-ip"), None, Some("0.0.0.0"), None);
107+
assert_eq!(addr.ip(), DEFAULT_HOST);
108+
}
109+
110+
#[test]
111+
fn invalid_env_port_falls_back_to_default() {
112+
let addr = resolve_bind_addr(None, Some("abc"), None, Some(3000));
113+
assert_eq!(addr.port(), DEFAULT_PORT);
114+
}
115+
116+
#[test]
117+
fn invalid_config_host_falls_back_to_default() {
118+
let addr = resolve_bind_addr(None, None, Some("not-an-ip"), None);
119+
assert_eq!(addr.ip(), DEFAULT_HOST);
120+
}
121+
122+
#[test]
123+
fn port_zero_from_env_falls_back_to_default() {
124+
let addr = resolve_bind_addr(None, Some("0"), None, None);
125+
assert_eq!(addr.port(), DEFAULT_PORT);
126+
}
127+
128+
#[test]
129+
fn port_zero_from_config_falls_back_to_default() {
130+
let addr = resolve_bind_addr(None, None, None, Some(0));
131+
assert_eq!(addr.port(), DEFAULT_PORT);
132+
}
133+
134+
#[test]
135+
fn ipv6_host_from_env() {
136+
let addr = resolve_bind_addr(Some("::1"), None, None, None);
137+
assert_eq!(addr.ip(), "::1".parse::<IpAddr>().unwrap());
138+
}
139+
140+
#[test]
141+
fn ipv6_host_from_config() {
142+
let addr = resolve_bind_addr(None, None, Some("::"), Some(3000));
143+
assert_eq!(addr.ip(), "::".parse::<IpAddr>().unwrap());
144+
assert_eq!(addr.port(), 3000);
145+
}
146+
}

crates/edgezero-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Core primitives for building portable edge workloads across edge adapters.
22
3+
pub mod addr;
34
pub mod app;
45
pub mod body;
56
pub mod compression;

0 commit comments

Comments
 (0)