Skip to main content

futu_server/
bind_hint.rs

1//! Operator-facing listener bind error hints shared by TCP/WS/Telnet/REST/gRPC.
2
3use std::fmt;
4
5fn listen_port(addr: &str) -> Option<&str> {
6    let trimmed = addr.trim();
7    if let Some((_, port)) = trimmed.rsplit_once("]:") {
8        return (!port.is_empty()).then_some(port);
9    }
10    trimmed
11        .rsplit_once(':')
12        .and_then(|(_, port)| (!port.is_empty()).then_some(port))
13}
14
15/// Build a stable, grep-friendly bind failure message.
16#[must_use]
17pub fn bind_error_message(
18    surface: &str,
19    flag_name: &str,
20    listen_addr: &str,
21    error: impl fmt::Display,
22) -> String {
23    let inspect = listen_port(listen_addr)
24        .map(|port| format!("lsof -nP -iTCP:{port} -sTCP:LISTEN"))
25        .unwrap_or_else(|| "lsof -nP -iTCP -sTCP:LISTEN".to_string());
26
27    format!(
28        "{surface} listener failed to bind {listen_addr}: {error}. \
29         Hint: another futu-opend or local process may already own this port. \
30         Change {flag_name}, stop the old process, or inspect with `{inspect}`."
31    )
32}
33
34/// Build a warning for the pre-bind startup probe that sees an existing
35/// listener on the configured port.
36#[must_use]
37pub fn port_conflict_message(surface: &str, flag_name: &str, listen_addr: &str) -> String {
38    let inspect = listen_port(listen_addr)
39        .map(|port| format!("lsof -nP -iTCP:{port} -sTCP:LISTEN"))
40        .unwrap_or_else(|| "lsof -nP -iTCP -sTCP:LISTEN".to_string());
41
42    format!(
43        "{surface} port {listen_addr} already accepts TCP connections. \
44         Hint: another futu-opend or local process may be running. \
45         Change {flag_name}, stop the old process, or inspect with `{inspect}`."
46    )
47}
48
49/// Preserve the original `std::io::ErrorKind` while replacing the message with
50/// an operator-actionable hint.
51pub fn io_bind_error(
52    surface: &str,
53    flag_name: &str,
54    listen_addr: &str,
55    error: std::io::Error,
56) -> std::io::Error {
57    let kind = error.kind();
58    std::io::Error::new(
59        kind,
60        bind_error_message(surface, flag_name, listen_addr, error),
61    )
62}
63
64#[cfg(test)]
65mod tests;