use std::time::Duration;
const SCAN_START: u16 = 9000;
const SCAN_END: u16 = 9010;
const DEFAULT_PORT: u16 = 9000;
pub fn should_auto_spawn(explicit_cli: Option<&str>, env_broker: Option<&str>) -> bool {
explicit_cli.is_none() && env_broker.is_none()
}
fn find_running_broker<F>(mut probe: F) -> Option<String>
where
F: FnMut(u16) -> bool,
{
for port in SCAN_START..=SCAN_END {
if probe(port) {
return Some(format!("http://localhost:{}", port));
}
}
None
}
fn real_probe(port: u16) -> bool {
crate::broker::uri::probe_local(port).is_some()
}
pub fn should_enable_p2p(mesh_ip: Option<&str>) -> bool {
mesh_ip
.map(|ip| ip.starts_with("10.13.13."))
.unwrap_or(false)
}
pub fn ensure_local_broker(verbose: bool) -> Result<String, String> {
if let Some(url) = find_running_broker(real_probe) {
return Ok(url);
}
if verbose {
println!(" starting local broker…");
}
let exe = std::env::current_exe()
.map_err(|e| format!("could not resolve current executable: {}", e))?;
crate::credentials::load_into_env();
crate::broker::apply_user_broker_defaults();
let mut cmd = std::process::Command::new(&exe);
cmd.arg("-d").arg("broker");
if should_enable_p2p(crate::broker::discovery::get_mesh_ip().as_deref()) {
cmd.env("ZAKURO_P2P", "true");
}
cmd.stdin(std::process::Stdio::null());
cmd.stdout(std::process::Stdio::null());
cmd.stderr(std::process::Stdio::null());
cmd.spawn()
.map_err(|e| format!("could not spawn local broker: {}", e))?;
let deadline = std::time::Instant::now() + Duration::from_secs(10);
while std::time::Instant::now() < deadline {
if real_probe(DEFAULT_PORT) {
return Ok(format!("http://localhost:{}", DEFAULT_PORT));
}
std::thread::sleep(Duration::from_millis(400));
}
Err("could not start a local broker — run `zc broker` manually to see errors".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_auto_spawn_when_nothing_explicit() {
assert!(should_auto_spawn(None, None));
}
#[test]
fn should_not_auto_spawn_with_explicit_cli_arg() {
assert!(!should_auto_spawn(Some("zc://node-foo"), None));
}
#[test]
fn should_not_auto_spawn_with_env_broker_set() {
assert!(!should_auto_spawn(None, Some("zc://node-foo")));
}
#[test]
fn should_not_auto_spawn_with_both_set() {
assert!(!should_auto_spawn(
Some("zc://node-foo"),
Some("zc://node-bar")
));
}
#[test]
fn mesh_ip_present_enables_p2p() {
assert!(should_enable_p2p(Some("10.13.13.6")));
}
#[test]
fn no_mesh_ip_disables_p2p() {
assert!(!should_enable_p2p(None));
}
#[test]
fn non_mesh_ip_disables_p2p() {
assert!(!should_enable_p2p(Some("192.168.1.50")));
}
#[test]
fn find_running_broker_reuses_first_hit() {
let seen = std::cell::RefCell::new(Vec::new());
let url = find_running_broker(|port| {
seen.borrow_mut().push(port);
port == 9003
});
assert_eq!(url, Some("http://localhost:9003".to_string()));
assert_eq!(*seen.borrow(), vec![9000, 9001, 9002, 9003]);
}
#[test]
fn find_running_broker_none_when_all_fail() {
let url = find_running_broker(|_port| false);
assert_eq!(url, None);
}
}