use crate::server::HttpServer;
use crate::utils::error::gateway_error::GatewayError;
use tracing::{info, warn};
impl HttpServer {
pub async fn shutdown_signal() {
let ctrl_c = async {
match tokio::signal::ctrl_c().await {
Ok(()) => info!("Received Ctrl+C signal, shutting down gracefully"),
Err(e) => warn!("Failed to install Ctrl+C handler: {}", e),
}
};
#[cfg(unix)]
let terminate = async {
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
Ok(mut signal) => {
signal.recv().await;
info!("Received terminate signal, shutting down gracefully");
}
Err(e) => {
warn!("Failed to install SIGTERM handler: {}", e);
std::future::pending::<()>().await;
}
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}
pub(crate) fn format_bind_error(
error: std::io::Error,
bind_addr: &str,
port: u16,
) -> GatewayError {
let error_str = error.to_string();
if error_str.contains("Address already in use")
|| error_str.contains("os error 48")
|| error_str.contains("os error 98")
{
let message = format!(
r#"
┌─────────────────────────────────────────────────────────────────┐
│ ❌ Error: Port {} is already in use
├─────────────────────────────────────────────────────────────────┤
│ Possible solutions:
│
│ 1. Kill the existing process:
│ lsof -ti:{} | xargs kill -9
│
│ 2. Use a different port:
│ --port {} or PORT={}
│
│ 3. Check what's using it:
│ lsof -i:{}
└─────────────────────────────────────────────────────────────────┘
"#,
port,
port,
port + 1,
port + 1,
port
);
GatewayError::server(message)
} else if error_str.contains("Permission denied") || error_str.contains("os error 13") {
let message = format!(
r#"
┌─────────────────────────────────────────────────────────────────┐
│ ❌ Error: Permission denied for port {}
├─────────────────────────────────────────────────────────────────┤
│ Possible solutions:
│
│ 1. Use a port >= 1024 (non-privileged):
│ --port 8000 or PORT=8000
│
│ 2. Run with elevated privileges (not recommended):
│ sudo ./gateway
└─────────────────────────────────────────────────────────────────┘
"#,
port
);
GatewayError::server(message)
} else {
GatewayError::server(format!("Failed to bind to {}: {}", bind_addr, error))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error, ErrorKind};
#[test]
fn test_format_bind_error_address_in_use() {
let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
let error_msg = result.to_string();
assert!(error_msg.contains("8080"));
assert!(error_msg.contains("already in use"));
assert!(error_msg.contains("8081")); }
#[test]
fn test_format_bind_error_os_error_48() {
let error = Error::other("os error 48");
let result = HttpServer::format_bind_error(error, "0.0.0.0:3000", 3000);
let error_msg = result.to_string();
assert!(error_msg.contains("3000"));
assert!(error_msg.contains("3001")); }
#[test]
fn test_format_bind_error_os_error_98() {
let error = Error::other("os error 98");
let result = HttpServer::format_bind_error(error, "127.0.0.1:9000", 9000);
let error_msg = result.to_string();
assert!(error_msg.contains("9000"));
}
#[test]
fn test_format_bind_error_permission_denied() {
let error = Error::new(ErrorKind::PermissionDenied, "Permission denied");
let result = HttpServer::format_bind_error(error, "0.0.0.0:80", 80);
let error_msg = result.to_string();
assert!(error_msg.contains("80"));
assert!(error_msg.contains("Permission denied"));
assert!(error_msg.contains("1024")); }
#[test]
fn test_format_bind_error_os_error_13() {
let error = Error::other("os error 13");
let result = HttpServer::format_bind_error(error, "0.0.0.0:443", 443);
let error_msg = result.to_string();
assert!(error_msg.contains("443"));
assert!(error_msg.contains("Permission denied"));
}
#[test]
fn test_format_bind_error_generic() {
let error = Error::other("Network unreachable");
let result = HttpServer::format_bind_error(error, "192.168.1.1:8080", 8080);
let error_msg = result.to_string();
assert!(error_msg.contains("Failed to bind"));
assert!(error_msg.contains("192.168.1.1:8080"));
assert!(error_msg.contains("Network unreachable"));
}
#[test]
fn test_format_bind_error_suggested_port_8000() {
let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
let result = HttpServer::format_bind_error(error, "0.0.0.0:8000", 8000);
assert!(result.to_string().contains("8001"));
}
#[test]
fn test_format_bind_error_suggested_port_3000() {
let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
let result = HttpServer::format_bind_error(error, "0.0.0.0:3000", 3000);
assert!(result.to_string().contains("3001"));
}
#[test]
fn test_format_bind_error_contains_emoji() {
let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
let error_msg = result.to_string();
assert!(error_msg.contains("❌")); }
#[test]
fn test_format_bind_error_contains_lsof_command() {
let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
let error_msg = result.to_string();
assert!(error_msg.contains("lsof")); }
#[test]
fn test_format_bind_error_privileged_port() {
let error = Error::new(ErrorKind::PermissionDenied, "Permission denied");
let result = HttpServer::format_bind_error(error, "0.0.0.0:22", 22);
let error_msg = result.to_string();
assert!(error_msg.contains("22"));
assert!(error_msg.contains("8000")); }
#[test]
fn test_format_bind_error_connection_refused() {
let error = Error::new(ErrorKind::ConnectionRefused, "Connection refused");
let result = HttpServer::format_bind_error(error, "localhost:8080", 8080);
let error_msg = result.to_string();
assert!(error_msg.contains("Connection refused"));
}
#[test]
fn test_format_bind_error_invalid_input() {
let error = Error::new(ErrorKind::InvalidInput, "Invalid address format");
let result = HttpServer::format_bind_error(error, "invalid:addr", 0);
let error_msg = result.to_string();
assert!(error_msg.contains("Failed to bind"));
}
#[test]
fn test_format_bind_error_returns_gateway_error() {
let error = Error::new(ErrorKind::AddrInUse, "Address already in use");
let result = HttpServer::format_bind_error(error, "0.0.0.0:8080", 8080);
let error_str = result.to_string();
assert!(!error_str.is_empty());
assert!(error_str.contains("8080"));
}
}