1use 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#[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#[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
49pub 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;