deboa-compio 0.1.4

A friendly rest client on top of hyper.
Documentation
use crate::cert::{DeboaCertificate, DeboaIdentity};
use compio::net::TcpStream;
use compio_tls::{
    native_tls::TlsConnector,
    native_tls::{Certificate, Identity},
    TlsStream,
};
use deboa::{
    errors::{ConnectionError, DeboaError},
    Result,
};

#[inline]
pub(crate) fn alpn() -> &'static [&'static str] {
    &[
        #[cfg(feature = "http3")]
        "h3",
        #[cfg(feature = "http2")]
        "h2",
        #[cfg(feature = "http1")]
        "http/1.1",
    ]
}

pub struct TlsConnectionBuilder<'a> {
    tcp_stream: TcpStream,
    host: &'a str,
    identity: Option<&'a DeboaIdentity>,
    certificate: Option<&'a DeboaCertificate>,
    skip_server_verification: bool,
    alpn: &'a [&'a str],
}

impl<'a> TlsConnectionBuilder<'a> {
    pub fn new(tcp_stream: TcpStream, host: &'a str) -> Self {
        Self {
            tcp_stream,
            host,
            identity: None,
            certificate: None,
            skip_server_verification: false,
            alpn: alpn(),
        }
    }

    pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self {
        self.identity = identity;
        self
    }

    pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self {
        self.certificate = certificate;
        self
    }

    pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self {
        self.skip_server_verification = skip_server_verification;
        self
    }

    pub fn alpn(mut self, alpn: &'a [&str]) -> Self {
        self.alpn = alpn;
        self
    }

    pub async fn connect(self) -> Result<TlsStream<TcpStream>> {
        let mut builder = TlsConnector::builder();

        let builder = if self.skip_server_verification {
            builder
                .danger_accept_invalid_certs(true)
                .danger_accept_invalid_hostnames(true)
        } else {
            &mut builder
        };

        let builder = builder.request_alpns(self.alpn);

        let builder = if let Some(ca) = self.certificate {
            let cert: Certificate = ca
                .try_into()
                .map_err(|e| {
                    DeboaError::Connection(ConnectionError::Tls {
                        message: format!("Invalid CA certificate: {}", e),
                    })
                })?;
            builder.add_root_certificate(cert)
        } else {
            builder
        };

        let builder = if let Some(identity) = self.identity {
            let ident: Identity = identity
                .try_into()
                .map_err(|e| {
                    DeboaError::Connection(ConnectionError::Tls {
                        message: format!("Invalid client identity: {}", e),
                    })
                })?;
            builder.identity(ident)
        } else {
            builder
        };

        let connector = builder
            .build()
            .map_err(|e| {
                DeboaError::Connection(ConnectionError::Tls {
                    message: format!("Could not build TLS connector: {}", e),
                })
            })?;

        let connector = compio_tls::TlsConnector::from(connector);

        let stream = connector
            .connect(self.host, self.tcp_stream)
            .await
            .map_err(|e| {
                DeboaError::Connection(ConnectionError::Tls {
                    message: format!("Could not connect to server: {}", e),
                })
            })?;

        Ok(stream)
    }
}