1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
use std::{result, time::Duration};

use reqwest::blocking::{Client as HttpClient, ClientBuilder as HttpClientBuilder};
use thiserror::Error;

use crate::Pkcs12Certificate;

/// Tempo padrão de timeout para conexão de client HTTP.
pub const CLIENT_CONNECT_TIMEOUT: u64 = 5;

/// Tempo padrão de timeout para transmissão de dados de client HTTP.
pub const CLIENT_TIMEOUT: u64 = 30;

#[derive(Error, Debug)]
/// Tipo para tratar erros relacionados a I/O e ao client HTTP.
pub enum ClientError {
    /// Erros relacionados a HTTP.
    #[error(transparent)]
    HttpClient(#[from] reqwest::Error),
}

/// Tipo para tratar retorno do client HTTP.
pub type ClientResult = result::Result<Vec<u8>, ClientError>;

/// Client HTTP com suporte a TLS e compressão de dados.
#[derive(Clone, Debug)]
pub struct Client {
    inner: HttpClient,
}

impl Client {
    /// Executa requisição ao servidor informando URL e informações de SOAP como action e XML.
    pub fn execute(&self, url: &str, action: &str, xml: Vec<u8>) -> ClientResult {
        //TODO: tentativas de reconexão
        let mut body = Vec::new();
        let mut res = self
            .inner
            .post(url)
            .header("Content-Type", "application/soap+xml; charset=utf-8")
            .header("SOAPAction", action)
            .body(xml)
            .send()?;
        res.copy_to(&mut body)?;
        Ok(body)
    }
}

/// Construtor de clients HTTP usando [build pattern](https://en.wikipedia.org/wiki/Builder_pattern).
#[derive(Debug)]
pub struct ClientBuilder {
    inner: HttpClientBuilder,
}

/// Tipo para tratar retorno do builder de client HTTP.
pub type ClientBuilderResult = result::Result<Client, ClientError>;

impl ClientBuilder {
    /// Cria uma nova instância do builder de client HTTP.
    pub fn new() -> Self {
        Self {
            inner: HttpClientBuilder::new()
                .danger_accept_invalid_certs(true)
                .gzip(true)
                .user_agent("Rust-Fiscalidade")
                .timeout(Duration::from_secs(CLIENT_TIMEOUT))
                .connect_timeout(Duration::from_secs(CLIENT_CONNECT_TIMEOUT)),
        }
    }

    /// Aplica certificado PKCS12 ao client HTTP criado.
    pub fn set_pkcs12(self, pkcs12: Pkcs12Certificate) -> Self {
        self.with_inner(|inner| inner.identity(pkcs12.into_inner()))
    }

    /// Aplica tempo máximo de timeout para conexão do client HTTP criado.
    pub fn set_connect_timeout(self, timeout: Duration) -> Self {
        self.with_inner(|inner| inner.connect_timeout(timeout))
    }

    /// Aplica tempo máximo de timeout para transmissão de dados do client HTTP criado.
    pub fn set_timeout(self, timeout: Duration) -> Self {
        self.with_inner(|inner| inner.timeout(timeout))
    }

    /// Torna o client HTTP criado mais verboso, i.e. emite mais informações de log.
    pub fn set_verbose(self, verbose: bool) -> Self {
        self.with_inner(move |inner| inner.connection_verbose(verbose))
    }

    /// Constrói novo client HTTP pré-configurado.
    pub fn build(self) -> ClientBuilderResult {
        Ok(Client {
            inner: self.inner.build()?,
        })
    }

    #[inline]
    fn with_inner<F>(mut self, func: F) -> Self
    where
        F: FnOnce(HttpClientBuilder) -> HttpClientBuilder,
    {
        self.inner = func(self.inner);
        self
    }
}