use async_trait::async_trait;
use tokio::net::TcpStream;
use super::traits::{Transport, TransportConfig};
use crate::error::ConnectError;
#[derive(Debug, Clone)]
pub struct TcpTransport {
config: TransportConfig,
}
impl TcpTransport {
#[must_use]
pub fn new() -> Self {
Self {
config: TransportConfig::default(),
}
}
#[must_use]
pub const fn with_config(config: TransportConfig) -> Self {
Self { config }
}
#[must_use]
pub const fn config(&self) -> &TransportConfig {
&self.config
}
}
impl Default for TcpTransport {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Transport for TcpTransport {
type Stream = TcpStream;
async fn connect(&self, host: &str, port: u16) -> Result<Self::Stream, ConnectError> {
let addr = format!("{host}:{port}");
let stream = tokio::time::timeout(self.config.connect_timeout, TcpStream::connect(&addr))
.await
.map_err(|_| ConnectError::Timeout(self.config.connect_timeout))?
.map_err(|e| {
tracing::debug!(addr = %addr, error = ?e, "TCP connection failed");
ConnectError::TcpConnect(e.to_string())
})?;
if self.config.disable_nagle {
stream.set_nodelay(true).map_err(|e| {
tracing::warn!(error = ?e, "Failed to set TCP_NODELAY");
ConnectError::Io(e.to_string())
})?;
}
tracing::debug!(addr = %addr, "TCP connection established");
Ok(stream)
}
fn name(&self) -> &'static str {
"tcp"
}
fn default_port(&self) -> u16 {
80
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tcp_transport_creation() {
let transport = TcpTransport::new();
assert_eq!(transport.name(), "tcp");
assert_eq!(transport.default_port(), 80);
}
#[test]
fn test_tcp_transport_config() {
let config = TransportConfig::low_latency();
let transport = TcpTransport::with_config(config);
assert!(transport.config().disable_nagle);
}
}