From 57a0f04185b209d867eb0285a0718bf301182e9b Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Sat, 14 Dec 2019 03:12:52 +0300 Subject: [PATCH 1/4] Typed errors --- src/client.rs | 48 ++++++++++++++++-------------------------------- src/error.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 3 files changed, 63 insertions(+), 32 deletions(-) create mode 100644 src/error.rs diff --git a/src/client.rs b/src/client.rs index 1ad6555..973b2a5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,10 +7,9 @@ use hyper::{client::connect::HttpConnector, service::Service, Uri}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tls::TlsConnector; +use crate::error::ConnectorError; use crate::stream::MaybeHttpsStream; -type BoxError = Box; - /// A Connector for the `https` scheme. #[derive(Clone)] pub struct HttpsConnector { @@ -85,16 +84,16 @@ where T: Service, T::Response: AsyncRead + AsyncWrite + Send + Unpin, T::Future: Send + 'static, - T::Error: Into, + T::Error: Send + Sync + 'static, { type Response = MaybeHttpsStream; - type Error = BoxError; - type Future = HttpsConnecting; + type Error = ConnectorError; + type Future = HttpsConnecting; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self.http.poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), + Poll::Ready(Err(e)) => Poll::Ready(Err(ConnectorError::HttpConnector(e))), Poll::Pending => Poll::Pending, } } @@ -103,20 +102,19 @@ where let is_https = dst.scheme_str() == Some("https"); // Early abort if HTTPS is forced but can't be used if !is_https && self.force_https { - return err(ForceHttpsButUriNotHttps.into()); + return err(ConnectorError::ForceHttpsButUriNotHttps); } - let host = dst.host() - .unwrap_or("") - .to_owned(); + let host = dst.host().unwrap_or("").to_owned(); let connecting = self.http.call(dst); let tls = self.tls.clone(); let fut = async move { - let tcp = connecting.await.map_err(Into::into)?; + let tcp = connecting.await.map_err(ConnectorError::HttpConnector)?; let maybe = if is_https { let tls = tls .connect(&host, tcp) - .await?; + .await + .map_err(|e| ConnectorError::NativeTls(e))?; MaybeHttpsStream::Https(tls) } else { MaybeHttpsStream::Http(tcp) @@ -127,39 +125,25 @@ where } } -fn err(e: BoxError) -> HttpsConnecting { +fn err(e: E) -> HttpsConnecting { HttpsConnecting(Box::pin(async { Err(e) })) } -type BoxedFut = - Pin, BoxError>> + Send>>; +type BoxedFut = Pin, E>> + Send>>; /// A Future representing work to connect to a URL, and a TLS handshake. -pub struct HttpsConnecting(BoxedFut); +pub struct HttpsConnecting(BoxedFut); -impl Future for HttpsConnecting { - type Output = Result, BoxError>; +impl Future for HttpsConnecting { + type Output = Result, E>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.0).poll(cx) } } -impl fmt::Debug for HttpsConnecting { +impl fmt::Debug for HttpsConnecting { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.pad("HttpsConnecting") } } - -// ===== Custom Errors ===== - -#[derive(Debug)] -struct ForceHttpsButUriNotHttps; - -impl fmt::Display for ForceHttpsButUriNotHttps { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("https required but URI was not https") - } -} - -impl std::error::Error for ForceHttpsButUriNotHttps {} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..788bcda --- /dev/null +++ b/src/error.rs @@ -0,0 +1,45 @@ +/// ConnectorError represents a HttpsConnector error. +pub enum ConnectorError { + /// An https:// URI was provided when the force_https option was on. + ForceHttpsButUriNotHttps, + /// Underlying HttpConnector failed when setting up an HTTP connection. + HttpConnector(E), + /// `native_tls` failed when setting up a TLS connection. + NativeTls(native_tls::Error), +} + +impl std::fmt::Debug for ConnectorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConnectorError::ForceHttpsButUriNotHttps => { + write!(f, "ConnectorError::ForceHttpsButUriNotHttps") + } + ConnectorError::HttpConnector(err) => { + write!(f, "ConnectorError::HttpConnector({:?})", err) + } + ConnectorError::NativeTls(err) => write!(f, "ConnectorError::NativeTls({:?})", err), + } + } +} + +impl std::fmt::Display for ConnectorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConnectorError::ForceHttpsButUriNotHttps => { + write!(f, "https required but URI was not https") + } + ConnectorError::HttpConnector(err) => write!(f, "http connector error: {}", err), + ConnectorError::NativeTls(err) => write!(f, "native tls error: {}", err), + } + } +} + +impl std::error::Error for ConnectorError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ConnectorError::ForceHttpsButUriNotHttps => None, + ConnectorError::HttpConnector(err) => Some(err), + ConnectorError::NativeTls(err) => Some(err), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 94ab3d3..9390c9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,8 @@ pub extern crate native_tls; pub use client::{HttpsConnecting, HttpsConnector}; pub use stream::{MaybeHttpsStream, TlsStream}; +pub use error::ConnectorError; mod client; mod stream; +mod error; From f13e4e2cca6208daa444b219d3e2a49753f25c57 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Sat, 14 Dec 2019 03:29:44 +0300 Subject: [PATCH 2/4] Rename ConnectorError to HttpsConnectorError for clarity Useful in the environment where multiple connectors exist to disambiguate which connector failed --- src/client.rs | 14 ++++++++------ src/error.rs | 34 ++++++++++++++++++---------------- src/lib.rs | 4 ++-- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/client.rs b/src/client.rs index 973b2a5..c921995 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,7 +7,7 @@ use hyper::{client::connect::HttpConnector, service::Service, Uri}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tls::TlsConnector; -use crate::error::ConnectorError; +use crate::error::HttpsConnectorError; use crate::stream::MaybeHttpsStream; /// A Connector for the `https` scheme. @@ -87,13 +87,13 @@ where T::Error: Send + Sync + 'static, { type Response = MaybeHttpsStream; - type Error = ConnectorError; + type Error = HttpsConnectorError; type Future = HttpsConnecting; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self.http.poll_ready(cx) { Poll::Ready(Ok(())) => Poll::Ready(Ok(())), - Poll::Ready(Err(e)) => Poll::Ready(Err(ConnectorError::HttpConnector(e))), + Poll::Ready(Err(e)) => Poll::Ready(Err(HttpsConnectorError::HttpConnector(e))), Poll::Pending => Poll::Pending, } } @@ -102,19 +102,21 @@ where let is_https = dst.scheme_str() == Some("https"); // Early abort if HTTPS is forced but can't be used if !is_https && self.force_https { - return err(ConnectorError::ForceHttpsButUriNotHttps); + return err(HttpsConnectorError::ForceHttpsButUriNotHttps); } let host = dst.host().unwrap_or("").to_owned(); let connecting = self.http.call(dst); let tls = self.tls.clone(); let fut = async move { - let tcp = connecting.await.map_err(ConnectorError::HttpConnector)?; + let tcp = connecting + .await + .map_err(HttpsConnectorError::HttpConnector)?; let maybe = if is_https { let tls = tls .connect(&host, tcp) .await - .map_err(|e| ConnectorError::NativeTls(e))?; + .map_err(|e| HttpsConnectorError::NativeTls(e))?; MaybeHttpsStream::Https(tls) } else { MaybeHttpsStream::Http(tcp) diff --git a/src/error.rs b/src/error.rs index 788bcda..1cd6e5b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ -/// ConnectorError represents a HttpsConnector error. -pub enum ConnectorError { +/// HttpsConnectorError represents a HttpsConnector error. +pub enum HttpsConnectorError { /// An https:// URI was provided when the force_https option was on. ForceHttpsButUriNotHttps, /// Underlying HttpConnector failed when setting up an HTTP connection. @@ -8,38 +8,40 @@ pub enum ConnectorError { NativeTls(native_tls::Error), } -impl std::fmt::Debug for ConnectorError { +impl std::fmt::Debug for HttpsConnectorError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ConnectorError::ForceHttpsButUriNotHttps => { - write!(f, "ConnectorError::ForceHttpsButUriNotHttps") + HttpsConnectorError::ForceHttpsButUriNotHttps => { + write!(f, "HttpsConnectorError::ForceHttpsButUriNotHttps") } - ConnectorError::HttpConnector(err) => { - write!(f, "ConnectorError::HttpConnector({:?})", err) + HttpsConnectorError::HttpConnector(err) => { + write!(f, "HttpsConnectorError::HttpConnector({:?})", err) + } + HttpsConnectorError::NativeTls(err) => { + write!(f, "HttpsConnectorError::NativeTls({:?})", err) } - ConnectorError::NativeTls(err) => write!(f, "ConnectorError::NativeTls({:?})", err), } } } -impl std::fmt::Display for ConnectorError { +impl std::fmt::Display for HttpsConnectorError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ConnectorError::ForceHttpsButUriNotHttps => { + HttpsConnectorError::ForceHttpsButUriNotHttps => { write!(f, "https required but URI was not https") } - ConnectorError::HttpConnector(err) => write!(f, "http connector error: {}", err), - ConnectorError::NativeTls(err) => write!(f, "native tls error: {}", err), + HttpsConnectorError::HttpConnector(err) => write!(f, "http connector error: {}", err), + HttpsConnectorError::NativeTls(err) => write!(f, "native tls error: {}", err), } } } -impl std::error::Error for ConnectorError { +impl std::error::Error for HttpsConnectorError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - ConnectorError::ForceHttpsButUriNotHttps => None, - ConnectorError::HttpConnector(err) => Some(err), - ConnectorError::NativeTls(err) => Some(err), + HttpsConnectorError::ForceHttpsButUriNotHttps => None, + HttpsConnectorError::HttpConnector(err) => Some(err), + HttpsConnectorError::NativeTls(err) => Some(err), } } } diff --git a/src/lib.rs b/src/lib.rs index 9390c9e..44fc7b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,9 +29,9 @@ pub extern crate native_tls; pub use client::{HttpsConnecting, HttpsConnector}; +pub use error::HttpsConnectorError; pub use stream::{MaybeHttpsStream, TlsStream}; -pub use error::ConnectorError; mod client; -mod stream; mod error; +mod stream; From 0707b2396aa419d844ff66402e424c2b735aa53e Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Sat, 14 Dec 2019 03:41:11 +0300 Subject: [PATCH 3/4] Use debug formatting utils at std::fmt::Debug impl for HttpsConnectorError --- src/error.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1cd6e5b..763d1a5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,14 +12,16 @@ impl std::fmt::Debug for HttpsConnectorError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { HttpsConnectorError::ForceHttpsButUriNotHttps => { - write!(f, "HttpsConnectorError::ForceHttpsButUriNotHttps") - } - HttpsConnectorError::HttpConnector(err) => { - write!(f, "HttpsConnectorError::HttpConnector({:?})", err) - } - HttpsConnectorError::NativeTls(err) => { - write!(f, "HttpsConnectorError::NativeTls({:?})", err) + f.write_str("HttpsConnectorError::ForceHttpsButUriNotHttps") } + HttpsConnectorError::HttpConnector(err) => f + .debug_tuple("HttpsConnectorError::HttpConnector") + .field(err) + .finish(), + HttpsConnectorError::NativeTls(err) => f + .debug_tuple("HttpsConnectorError::NativeTls") + .field(err) + .finish(), } } } From ba96eaf5c5749db62854009028813c55a0451c82 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Sat, 14 Dec 2019 04:59:53 +0300 Subject: [PATCH 4/4] Add __Nonexhaustive to HttpsConnectorError to make it nonexaustive to the API users --- src/error.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/error.rs b/src/error.rs index 763d1a5..0f9666c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,9 @@ pub enum HttpsConnectorError { HttpConnector(E), /// `native_tls` failed when setting up a TLS connection. NativeTls(native_tls::Error), + + #[doc(hidden)] + __Nonexhaustive, } impl std::fmt::Debug for HttpsConnectorError { @@ -22,6 +25,7 @@ impl std::fmt::Debug for HttpsConnectorError { .debug_tuple("HttpsConnectorError::NativeTls") .field(err) .finish(), + HttpsConnectorError::__Nonexhaustive => unimplemented!(), } } } @@ -34,6 +38,7 @@ impl std::fmt::Display for HttpsConnectorError { } HttpsConnectorError::HttpConnector(err) => write!(f, "http connector error: {}", err), HttpsConnectorError::NativeTls(err) => write!(f, "native tls error: {}", err), + HttpsConnectorError::__Nonexhaustive => unimplemented!(), } } } @@ -44,6 +49,7 @@ impl std::error::Error for HttpsConnector HttpsConnectorError::ForceHttpsButUriNotHttps => None, HttpsConnectorError::HttpConnector(err) => Some(err), HttpsConnectorError::NativeTls(err) => Some(err), + HttpsConnectorError::__Nonexhaustive => unimplemented!(), } } }