use std::time::Duration;
const SCAN_START: u16 = 9000;
const SCAN_END: u16 = 9010;
pub fn resolve(uri: &str) -> Result<String, String> {
if uri.starts_with("http://") || uri.starts_with("https://") {
return Err(format!(
"Plain HTTP URL '{}' is not allowed — use zc://node-name instead.",
uri
));
}
let rest = match uri.strip_prefix("zc://") {
Some(r) => r.trim_end_matches('/'),
None => return Err(format!("Invalid broker URI '{}'. Expected zc://node-name.", uri)),
};
if rest.is_empty() {
return Err("Empty broker name in zc:// URI.".to_string());
}
let (name, explicit_port): (&str, Option<u16>) = if let Some(colon) = rest.rfind(':') {
let port_str = &rest[colon + 1..];
match port_str.parse::<u16>() {
Ok(p) => (&rest[..colon], Some(p)),
Err(_) => (rest, None),
}
} else {
(rest, None)
};
let looks_like_ip = name.split('.').all(|octet| octet.parse::<u8>().is_ok())
&& name.contains('.');
if looks_like_ip {
let port = explicit_port.unwrap_or(9000);
return Ok(format!("http://{}:{}", name, port));
}
let agent = ureq::AgentBuilder::new()
.timeout_connect(Duration::from_millis(300))
.timeout_read(Duration::from_millis(500))
.build();
let ports: Vec<u16> = if let Some(p) = explicit_port {
vec![p]
} else {
(SCAN_START..=SCAN_END).collect()
};
let wildcard = name == "localhost" || name == "127.0.0.1";
for port in &ports {
let url = format!("http://localhost:{}/health", port);
if let Ok(resp) = agent.get(&url).call() {
if let Ok(text) = resp.into_string() {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&text) {
if wildcard || json["node_name"].as_str() == Some(name) {
return Ok(format!("http://localhost:{}", port));
}
}
}
}
}
if let Some(p) = explicit_port {
Err(format!("Broker '{}' not found on localhost:{}", name, p))
} else {
Err(format!(
"Broker '{}' not found on localhost:{}-{} — is it running?",
name, SCAN_START, SCAN_END
))
}
}