From eb7f8fa40c095578b32b28f690a590168e5b4278 Mon Sep 17 00:00:00 2001 From: Prince Karikari Date: Sun, 18 Oct 2026 11:57:16 -0500 Subject: [PATCH] Issue #241 solved: report rejected TCP addresses in the event log connect_tcp_with_validation_and_save validates before connecting, and on failure it set tcp_addr_validation_error and returned without calling tcp_connect. None of the paths that normally report a connection problem were reached, so the press left nothing in the event log. The only feedback was one line of plain text between the address box and the buttons, which is easy to miss. Log the rejection like every other connection failure, and give an empty field its own message. validate_tcp_addr returned "Invalid IP address" for an empty string, which reads as a typo when the greyed 127.0.0.1:9999 is placeholder text and nothing was typed at all. Tests cover both: an empty field and a malformed one each reach the event log and leave no TCP thread behind. --- relay/src/state.rs | 10 +++++++-- relay/src/update.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/relay/src/state.rs b/relay/src/state.rs index 18976ce..6dcff20 100644 --- a/relay/src/state.rs +++ b/relay/src/state.rs @@ -343,8 +343,14 @@ impl State { } pub fn validate_tcp_addr(address: &str) -> Result<()> { - let _parsed = address - .trim() + let trimmed = address.trim(); + // An empty field is its own mistake. Reporting it as "invalid" reads as + // though something was typed wrong, when the greyed 127.0.0.1:9999 is + // placeholder text and nothing was typed at all. + if trimmed.is_empty() { + bail!("No address entered. Type one, for example 127.0.0.1:9999"); + } + let _parsed = trimmed .parse::() .map_err(|_| anyhow!("Invalid IP address. Use format: 127.0.0.1:9999"))?; Ok(()) diff --git a/relay/src/update.rs b/relay/src/update.rs index b8acc74..3f4faee 100644 --- a/relay/src/update.rs +++ b/relay/src/update.rs @@ -24,7 +24,13 @@ fn connect_tcp_with_validation_and_save(state: &mut State, address: String) { } } Err(e) => { - state.tcp_addr_validation_error = Some(e.to_string()); + let msg = e.to_string(); + // Record it like every other connection failure. This path returns + // before tcp_connect, so without this the event log never hears that + // the press did anything, and the inline hint under the field is easy + // to miss. + state.log_event(format!("TCP connect failed: {}", msg)); + state.tcp_addr_validation_error = Some(msg); } } } @@ -426,6 +432,51 @@ mod tests { let _ = state.tcp_disconnect(); } + #[test] + fn empty_address_is_reported_in_the_event_log() { + let mut state = State::default(); + assert!(state.tcp_addr_field.is_empty(), "field should start empty"); + + let _ = update(&mut state, Message::ConnectTcp); + + // The inline hint alone is easy to miss, so the panel has to show it too. + assert!( + state + .event_log + .iter() + .any(|e| e.contains("TCP connect failed") && e.contains("No address entered")), + "empty address missing from the event log: {:?}", + state.event_log + ); + // An empty field is not the same mistake as a typo. + let inline = state + .tcp_addr_validation_error + .clone() + .expect("no inline validation error"); + assert!(inline.starts_with("No address entered"), "unexpected: {inline}"); + assert!(state.tcp_thread_handle.is_none(), "should not have connected"); + } + + #[test] + fn malformed_address_is_reported_in_the_event_log() { + let mut state = State::default(); + // Deliberately not a host name: #240 may start accepting those, and this + // test is about reporting, not about which forms are valid. + state.tcp_addr_field = "127.0.0.1".to_string(); + + let _ = update(&mut state, Message::ConnectTcp); + + assert!( + state + .event_log + .iter() + .any(|e| e.contains("TCP connect failed") && e.contains("Invalid IP address")), + "malformed address missing from the event log: {:?}", + state.event_log + ); + assert!(state.tcp_thread_handle.is_none(), "should not have connected"); + } + #[test] fn send_packet_with_every_toggle_off_reports_an_error() { let mut state = State::default();