apibrasil 1.0.0

SDK oficial Rust da plataforma APIBrasil: WhatsApp, SMS, consultas de CPF/CNPJ, veiculos, CEP, correios, pagamentos PIX/boleto e mais.
Documentation
//! SDK oficial Rust da plataforma [APIBrasil](https://apibrasil.com.br) —
//! WhatsApp, SMS, consultas de CPF/CNPJ, veículos, CEP, correios,
//! pagamentos PIX/boleto e muito mais.
//!
//! ```no_run
//! use apibrasil::{ApiBrasil, Config};
//! use serde_json::json;
//!
//! #[tokio::main]
//! async fn main() -> apibrasil::Result<()> {
//!     let api = ApiBrasil::new(
//!         Config::new()
//!             .bearer_token("SEU_BEARER_TOKEN") // JWT do login
//!             .device_token("SEU_DEVICE_TOKEN"), // device dos serviços device-based
//!     );
//!
//!     // WhatsApp
//!     api.whatsapp()
//!         .send_text(json!({ "number": "5511999999999", "text": "Olá! 👋" }))
//!         .await?;
//!
//!     // Consulta CNPJ (por créditos)
//!     let empresa = api.consulta().cnpj(json!({ "cnpj": "00000000000000" })).await?;
//!     println!("{:?}", empresa.data());
//!
//!     Ok(())
//! }
//! ```
//!
//! Credenciais não informadas são lidas das variáveis de ambiente
//! [`APIBRASIL_BEARER_TOKEN`](core::ENV_BEARER_TOKEN),
//! [`APIBRASIL_DEVICE_TOKEN`](core::ENV_DEVICE_TOKEN),
//! [`APIBRASIL_SECRET_KEY`](core::ENV_SECRET_KEY) e
//! [`APIBRASIL_BASE_URL`](core::ENV_BASE_URL) — basta
//! [`ApiBrasil::from_env`].
//!
//! # Como a plataforma funciona
//!
//! | Família | Autenticação | Exemplos |
//! | --- | --- | --- |
//! | **Device-based** | `Authorization: Bearer` + header `DeviceToken` | WhatsApp, SMS, veículos, CEP, correios, DDD, feriados, tradução, clima, OCR |
//! | **Por créditos** | apenas `Authorization: Bearer` (debita saldo) | [`consulta`](ApiBrasil::consulta): CPF, CNPJ, veículos, Serasa, CNH, telefone |
//!
//! # Features
//!
//! - `rustls-tls` (padrão) — TLS via rustls, sem depender de OpenSSL.
//! - `native-tls` — TLS pela biblioteca do sistema.
//! - `blocking` — cliente síncrono em [`blocking`], sobre um runtime
//!   Tokio próprio.

#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]

#[macro_use]
mod macros;

pub mod core;
pub mod generated;
pub mod legacy;
pub mod services;

#[cfg(feature = "blocking")]
#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))]
pub mod blocking;

use std::sync::Arc;

pub use self::core::{
    Config, Consulta, CreditResponse, DeviceResponse, Error, ErrorKind, FnHooks, Hooks, HttpClient,
    IntoBody, Json, Method, RequestHookInfo, RequestOptions, ReqwestTransport, ResponseData,
    ResponseHookInfo, ResponseType, Result, RetryConfig, RetryHookInfo, Transport,
    TransportRequest, TransportResponse, DEFAULT_BASE_URL, DEFAULT_TIMEOUT, USER_AGENT,
};
pub use self::services::data::{
    BulkService, CepService, ChipVirtualService, ConsultaService, CorreiosService, DadosService,
    DatabaseIpService, DddService, FipeService, GeolocationService, GeomatrixService,
    HolidaysService, LoteriasService, RecognizeService, TranslateService, UraService,
    VehiclesService, WeatherService,
};
pub use self::services::messaging::{
    EvolutionService, SmsService, WhatsAppService, WhatsMeowService,
};
pub use self::services::platform::{
    extract_token, requires_2fa, AccountService, AuthService, BearerRateLimitService,
    CatalogService, DevicesService, IpWhitelistService, PaymentsService, ReportsService,
};
pub use self::services::{BaseService, CreditService, DeviceProxyService};

/// Cliente da plataforma APIBrasil.
///
/// Todos os serviços compartilham o mesmo [`HttpClient`] — um
/// [`set_bearer_token`](Self::set_bearer_token) vale para todos. Clonar o
/// cliente é barato (um [`Arc`]) e mantém o estado compartilhado.
#[derive(Clone, Debug)]
pub struct ApiBrasil {
    http: Arc<HttpClient>,
    options: RequestOptions,
}

/// Alias de [`ApiBrasil`], para leitura equivalente às demais SDKs da
/// plataforma.
pub type Client = ApiBrasil;

impl ApiBrasil {
    /// Cria o cliente. Campos não informados na `config` vêm do
    /// ambiente.
    pub fn new(config: Config) -> Self {
        Self::with_http(Arc::new(HttpClient::new(config)))
    }

    /// Cria o cliente apenas com as credenciais do ambiente.
    pub fn from_env() -> Self {
        Self::new(Config::default())
    }

    /// Cria o cliente sobre um [`HttpClient`] já configurado.
    pub fn with_http(http: Arc<HttpClient>) -> Self {
        Self {
            http,
            options: RequestOptions::default(),
        }
    }

    /// Cliente HTTP interno (headers, base URL, retry, hooks, erros).
    pub fn http(&self) -> &Arc<HttpClient> {
        &self.http
    }

    /// Opções aplicadas a todas as chamadas deste cliente.
    pub fn options(&self) -> &RequestOptions {
        &self.options
    }

    /// Devolve um cliente que usa `options` em todas as chamadas —
    /// mesma conexão e mesmas credenciais.
    pub fn with_options(&self, options: RequestOptions) -> Self {
        Self {
            http: self.http.clone(),
            options,
        }
    }

    /// Configuração atual do cliente.
    pub fn config(&self) -> Config {
        self.http.config()
    }

    /// Define/atualiza o Bearer Token do cliente.
    pub fn set_bearer_token(&self, token: impl Into<String>) -> &Self {
        self.http.set_bearer_token(Some(token.into()));
        self
    }

    /// Remove o Bearer Token do cliente.
    pub fn clear_bearer_token(&self) -> &Self {
        self.http.set_bearer_token(None);
        self
    }

    /// Define/atualiza o DeviceToken do cliente.
    pub fn set_device_token(&self, token: impl Into<String>) -> &Self {
        self.http.set_device_token(Some(token.into()));
        self
    }

    /// Remove o DeviceToken do cliente.
    pub fn clear_device_token(&self) -> &Self {
        self.http.set_device_token(None);
        self
    }

    /// Devolve um novo cliente com as mesmas credenciais, mas apontando
    /// para outro device — útil para gerenciar vários
    /// números/instâncias.
    ///
    /// ```no_run
    /// # use apibrasil::ApiBrasil;
    /// # use serde_json::json;
    /// # async fn exemplo(api: ApiBrasil) -> apibrasil::Result<()> {
    /// let bot1 = api.with_device("device_token_1");
    /// let bot2 = api.with_device("device_token_2");
    ///
    /// bot1.whatsapp().send_text(json!({ "number": "5511999999999", "text": "do bot 1" })).await?;
    /// bot2.whatsapp().send_text(json!({ "number": "5511999999999", "text": "do bot 2" })).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn with_device(&self, device_token: impl Into<String>) -> Self {
        let mut config = self.http.config();
        config.device_token = Some(device_token.into());
        Self::new(config)
    }

    /// Porta de saída genérica: chama qualquer endpoint do gateway com os
    /// headers de autenticação já configurados. Use para rotas que ainda
    /// não têm método dedicado na SDK.
    ///
    /// ```no_run
    /// # use apibrasil::{ApiBrasil, Method};
    /// # use serde_json::json;
    /// # async fn exemplo(api: ApiBrasil) -> apibrasil::Result<()> {
    /// api.request(Method::Post, "/consulta/cpf/credits", json!({ "cpf": "00000000000" })).await?;
    /// api.request(Method::Get, "/reports/quick-stats", None).await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn request(&self, method: Method, path: &str, body: impl IntoBody) -> Result<Json> {
        self.http
            .request_json(method, path, body.into_body(), &self.options)
            .await
    }

    /// Como [`request`](Self::request), mas devolve o corpo decodificado
    /// sem normalizar em objeto JSON (útil quando a rota responde uma
    /// lista, texto ou bytes).
    pub async fn execute(
        &self,
        method: Method,
        path: &str,
        body: impl IntoBody,
    ) -> Result<ResponseData> {
        self.http
            .execute(method, path, body.into_body(), &self.options)
            .await
    }

    /// Baixa os bytes crus de uma rota (PDF de boleto, imagens...).
    pub async fn download(&self, path: &str) -> Result<Vec<u8>> {
        self.http
            .bytes(Method::Get, path, None, &self.options)
            .await
    }
}

/// Declara os acessores de serviço do cliente.
macro_rules! client_services {
    (
        $(
            $(#[$meta:meta])*
            $method:ident -> $service:ident
        ),* $(,)?
    ) => {
        impl ApiBrasil {
            $(
                $(#[$meta])*
                pub fn $method(&self) -> $service {
                    $service::new(self.http.clone()).with_options(self.options.clone())
                }
            )*
        }
    };
}

client_services! {
    /// Login, 2FA, cadastro, recuperação de senha e perfil.
    auth -> AuthService,
    /// CRUD de devices (criar, listar, atualizar, remover).
    devices -> DevicesService,

    /// WhatsApp device-based (`/whatsapp/{action}`).
    whatsapp -> WhatsAppService,
    /// Evolution API (`/evolution/{controller}/{action}`).
    evolution -> EvolutionService,
    /// WhatsMeow (`/whatsmeow/{action}`).
    whatsmeow -> WhatsMeowService,
    /// SMS (`/sms/{action}` e `/sms/send/credits`).
    sms -> SmsService,

    /// Dados cadastrais device-based (`/dados/cpf`, `/dados/cnpj`...).
    dados -> DadosService,
    /// Veículos por placa (`/vehicles/dados`, `/vehicles/fipe`).
    vehicles -> VehiclesService,
    /// Tabela FIPE (`/fipe/{action}`).
    fipe -> FipeService,
    /// Correios (`/correios/{action}`).
    correios -> CorreiosService,
    /// CEP + geolocalização (`/cep/{action}`).
    cep -> CepService,
    /// Geolocalização (`/geolocation/{action}`).
    geolocation -> GeolocationService,
    /// Matriz de distâncias (`/geomatrix/{action}`).
    geomatrix -> GeomatrixService,
    /// OCR / Google Vision (`/recognize/{action}`).
    recognize -> RecognizeService,
    /// DDD (`/ddd/{action}`).
    ddd -> DddService,
    /// Feriados (`/holidays/{action}`).
    holidays -> HolidaysService,
    /// Tradução (`/translate/{action}`).
    translate -> TranslateService,
    /// Clima (`/weather/{action}`).
    weather -> WeatherService,
    /// Loterias (`/loterias/{sorteio}/*`).
    loterias -> LoteriasService,
    /// GeoIP (`/database/ip`).
    database_ip -> DatabaseIpService,

    /// Consultas por crédito (`/consulta/{servico}/credits`).
    consulta -> ConsultaService,
    /// URA reversa / ligações (`/ura/call/*`).
    ura -> UraService,
    /// Chip virtual (`/chip/virtual/*`).
    chip_virtual -> ChipVirtualService,
    /// Execução em lote (`/bulk/*`).
    bulk -> BulkService,

    /// Catálogo de APIs, planos, documentações e servidores.
    catalog -> CatalogService,
    /// Saldo, faturas, notificações e tickets.
    account -> AccountService,
    /// Recargas e pagamentos (PIX, boleto, cartão).
    payments -> PaymentsService,
    /// Whitelist de IPs da conta.
    ip_whitelist -> IpWhitelistService,
    /// Rate limit por Bearer Token.
    bearer_rate_limit -> BearerRateLimitService,
    /// Relatórios e dashboard de consumo.
    reports -> ReportsService,
}

/// Autentica por email/senha e devolve um cliente já autenticado, junto
/// da sessão retornada pela plataforma.
///
/// ```no_run
/// # use serde_json::json;
/// # async fn exemplo() -> apibrasil::Result<()> {
/// let (api, sessao) = apibrasil::login(
///     json!({ "email": "voce@empresa.com.br", "password": "******" }),
///     Default::default(),
/// )
/// .await?;
/// # let _ = (api, sessao);
/// # Ok(())
/// # }
/// ```
///
/// Devolve erro quando a conta exige 2FA — nesse caso crie o cliente com
/// [`ApiBrasil::new`] e use [`AuthService::login`] +
/// [`AuthService::send_2fa`] + [`AuthService::verify_2fa`].
pub async fn login(credentials: impl IntoBody, config: Config) -> Result<(ApiBrasil, Json)> {
    let api = ApiBrasil::new(config);
    let session = api.auth().login(credentials).await?;

    if requires_2fa(&session) {
        return Err(Error::authentication(
            "Esta conta exige autenticação em dois fatores. Use auth().login() + auth().verify_2fa().",
        )
        .with_response(session));
    }

    Ok((api, session))
}