Repository: plabayo/rama (language: rust)

Issue:
use `TryInto<SocketAddress>` bound for bind/listen functions in rama
Currently (rama 0.2.0-alpha.6) we are using the `ToSocketAddrs` (https://docs.rs/tokio/latest/tokio/net/trait.ToSocketAddrs.html) trait for these functions:

- https://docs.rs/rama/latest/rama/http/server/struct.HttpServer.html#method.listen
- https://docs.rs/rama/latest/rama/http/server/struct.HttpServer.html#method.listen_with_state
- https://docs.rs/rama/latest/rama/tcp/server/struct.TcpListenerBuilder.html#method.bind
- https://docs.rs/rama/latest/rama/tcp/server/struct.TcpListener.html#method.bind

Problem with this is that it means we cannot for example pass our own types (such as `SocketAddress` ) to those functions, which feels weird.

Also implement `From<std::net::SocketAddr>`, `From<std::net::SocketAddrV4>` and `From<std::net::SocketAddrV6>` for `SocketADdress`., as seems we do not do that yet. Change also one or two examples (`in /examples/*`) to make use of a `SocketAddress` instead of a string, to show it is possible.


Discussion / hints:

Fix patch:
diff --git a/examples/http_listener_hello.rs b/examples/http_listener_hello.rs
index 9b936af7..ee4b834b 100644
--- a/examples/http_listener_hello.rs
+++ b/examples/http_listener_hello.rs
@@ -17,11 +17,14 @@
 //!
 //! You should see a response with `HTTP/1.1 200 OK` and a JSON body with the method and path of the request.
 
+use std::net::{IpAddr, Ipv4Addr};
+
 use rama::{
     http::{response::Json, server::HttpServer, Request},
     rt::Executor,
     service::service_fn,
 };
+use rama_net::address::SocketAddress;
 use serde_json::json;
 
 #[tokio::main]
@@ -29,7 +32,9 @@ async fn main() {
     let exec = Executor::default();
     HttpServer::auto(exec)
         .listen(
-            "127.0.0.1:62007",
+            // The below string type will also work
+            // "127.0.0.1:62007",
+            SocketAddress::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 62007),
             service_fn(|req: Request| async move {
                 Ok(Json(json!({
                     "method": req.method().as_str(),
diff --git a/examples/tcp_listener_hello.rs b/examples/tcp_listener_hello.rs
index 01ff472f..63ef7070 100644
--- a/examples/tcp_listener_hello.rs
+++ b/examples/tcp_listener_hello.rs
@@ -21,11 +21,17 @@ use rama::{
     service::service_fn,
     tcp::server::TcpListener,
 };
-use std::convert::Infallible;
+use rama_net::address::SocketAddress;
+use std::{
+    convert::Infallible,
+    net::{IpAddr, Ipv4Addr},
+};
 use tokio::io::AsyncWriteExt;
 
 const SRC: &str = include_str!("./tcp_listener_hello.rs");
-const ADDR: &str = "127.0.0.1:62500";
+// The below &str type will also work!
+// const ADDR: &str = "127.0.0.1:62500";
+const ADDR: SocketAddress = SocketAddress::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 62500);
 
 #[tokio::main]
 async fn main() {
diff --git a/rama-http-backend/src/server/service.rs b/rama-http-backend/src/server/service.rs
index f6aa36eb..4ecf5674 100644
--- a/rama-http-backend/src/server/service.rs
+++ b/rama-http-backend/src/server/service.rs
@@ -2,6 +2,7 @@
 
 use super::hyper_conn::HttpCoreConnServer;
 use super::HttpServeResult;
+use rama_core::error::BoxError;
 use rama_core::graceful::ShutdownGuard;
 use rama_core::rt::Executor;
 use rama_core::{Context, Service};
@@ -11,13 +12,13 @@ use rama_http_core::server::conn::auto::Http2Builder as InnerAutoHttp2Builder;
 use rama_http_core::server::conn::http1::Builder as Http1ConnBuilder;
 use rama_http_core::server::conn::http2::Builder as H2ConnBuilder;
 use rama_http_types::{IntoResponse, Request};
+use rama_net::address::SocketAddress;
 use rama_net::stream::Stream;
 use rama_tcp::server::TcpListener;
 use std::convert::Infallible;
 use std::fmt;
 use std::future::Future;
 use std::sync::Arc;
-use tokio::net::ToSocketAddrs;
 
 /// A builder for configuring and listening over HTTP using a [`Service`].
 ///
@@ -166,7 +167,7 @@ where
     where
         S: Service<(), Request, Response = Response, Error = Infallible>,
         Response: IntoResponse + Send + 'static,
-        A: ToSocketAddrs,
+        A: TryInto<SocketAddress, Error: Into<BoxError>>,
     {
         let tcp = TcpListener::bind(addr).await?;
         let service = HttpService::new(self.builder, service);
@@ -193,7 +194,7 @@ where
         State: Clone + Send + Sync + 'static,
         S: Service<State, Request, Response = Response, Error = Infallible>,
         Response: IntoResponse + Send + 'static,
-        A: ToSocketAddrs,
+        A: TryInto<SocketAddress, Error: Into<BoxError>>,
     {
         let tcp = TcpListener::build_with_state(state).bind(addr).await?;
         let service = HttpService::new(self.builder, service);
diff --git a/rama-net/src/address/socket_address.rs b/rama-net/src/address/socket_address.rs
index 0b305647..7001ce3f 100644
--- a/rama-net/src/address/socket_address.rs
+++ b/rama-net/src/address/socket_address.rs
@@ -3,7 +3,7 @@ use rama_core::error::{ErrorContext, OpaqueError};
 #[cfg(feature = "http")]
 use rama_http_types::HeaderValue;
 use std::fmt;
-use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
 use std::str::FromStr;
 
 /// An [`IpAddr`] with an associated port
@@ -57,6 +57,24 @@ impl From<&SocketAddr> for SocketAddress {
     }
 }
 
+impl From<SocketAddrV4> for SocketAddress {
+    fn from(value: SocketAddrV4) -> Self {
+        SocketAddress {
+            ip_addr: (*value.ip()).into(),
+            port: value.port(),
+        }
+    }
+}
+
+impl From<SocketAddrV6> for SocketAddress {
+    fn from(value: SocketAddrV6) -> Self {
+        SocketAddress {
+            ip_addr: (*value.ip()).into(),
+            port: value.port(),
+        }
+    }
+}
+
 impl From<SocketAddress> for SocketAddr {
     fn from(addr: SocketAddress) -> Self {
         SocketAddr::new(addr.ip_addr, addr.port)
@@ -131,6 +149,14 @@ impl TryFrom<String> for SocketAddress {
     }
 }
 
+impl TryFrom<&String> for SocketAddress {
+    type Error = OpaqueError;
+
+    fn try_from(value: &String) -> Result<Self, Self::Error> {
+        value.as_str().try_into()
+    }
+}
+
 impl TryFrom<&str> for SocketAddress {
     type Error = OpaqueError;
 
diff --git a/rama-tcp/src/server/listener.rs b/rama-tcp/src/server/listener.rs
index 36e61b09..1178c655 100644
--- a/rama-tcp/src/server/listener.rs
+++ b/rama-tcp/src/server/listener.rs
@@ -1,13 +1,15 @@
+use rama_core::error::BoxError;
 use rama_core::graceful::ShutdownGuard;
 use rama_core::rt::Executor;
 use rama_core::Context;
 use rama_core::Service;
+use rama_net::address::SocketAddress;
 use rama_net::stream::SocketInfo;
 use std::fmt;
 use std::pin::pin;
 use std::sync::Arc;
 use std::{io, net::SocketAddr};
-use tokio::net::{TcpListener as TokioTcpListener, TcpStream, ToSocketAddrs};
+use tokio::net::{TcpListener as TokioTcpListener, TcpStream};
 
 /// Builder for `TcpListener`.
 pub struct TcpListenerBuilder<S> {
@@ -93,8 +95,15 @@ where
     /// Binding with a port number of 0 will request that the OS assigns a port
     /// to this listener. The port allocated can be queried via the `local_addr`
     /// method.
-    pub async fn bind<A: ToSocketAddrs>(self, addr: A) -> io::Result<TcpListener<S>> {
-        let inner = TokioTcpListener::bind(addr).await?;
+    pub async fn bind<A: TryInto<SocketAddress, Error: Into<BoxError>>>(
+        self,
+        addr: A,
+    ) -> Result<TcpListener<S>, BoxError> {
+        let socket_addr = addr.try_into().map_err(Into::<BoxError>::into)?;
+        let tokio_socket_addr: SocketAddr = socket_addr.into();
+        let inner = TokioTcpListener::bind(tokio_socket_addr)
+            .await
+            .map_err(Into::<BoxError>::into)?;
 
         if let Some(ttl) = self.ttl {
             inner.set_ttl(ttl)?;
@@ -149,7 +158,9 @@ impl TcpListener<()> {
     /// Binding with a port number of 0 will request that the OS assigns a port
     /// to this listener. The port allocated can be queried via the `local_addr`
     /// method.
-    pub async fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
+    pub async fn bind<A: TryInto<SocketAddress, Error: Into<BoxError>>>(
+        addr: A,
+    ) -> Result<TcpListener<()>, BoxError> {
         TcpListenerBuilder::default().bind(addr).await
     }
 }


Test patch: