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
//! Interface legada da SDK, mantida por compatibilidade com o formato de
//! chamada usado pelas primeiras SDKs da plataforma
//! (`credentials`/`body`/`action` em uma string JSON).
//!
//! Prefira o cliente [`ApiBrasil`](crate::ApiBrasil), que cobre toda a
//! plataforma com métodos dedicados, erros tipados, retry e hooks.

use std::sync::Arc;

use serde_json::Value;

use crate::core::{Config, Error, HttpClient, Json, RequestOptions, Result, RetryConfig};

/// Cliente legado da plataforma.
///
/// Assim como nas versões anteriores, respostas de erro da API são
/// devolvidas decodificadas em vez de virarem `Err`.
///
/// ```no_run
/// # async fn exemplo() -> apibrasil::Result<()> {
/// use apibrasil::legacy::LegacyService;
///
/// let service = LegacyService::new();
///
/// let payload = serde_json::json!({
///     "action": "sendText",
///     "credentials": {
///         "DeviceToken": "SEU_DEVICE_TOKEN",
///         "BearerToken": "SEU_BEARER_TOKEN",
///     },
///     "body": { "number": "5511999999999", "text": "Hello World for Rust" },
/// })
/// .to_string();
///
/// let response = service.whatsapp(&payload).await?;
/// # let _ = response;
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct LegacyService {
    /// Base da API.
    pub server: String,
}

impl Default for LegacyService {
    fn default() -> Self {
        Self {
            server: "https://gateway.apibrasil.io/api/v2/".to_string(),
        }
    }
}

impl LegacyService {
    /// Cria o cliente legado apontando para o gateway padrão.
    pub fn new() -> Self {
        Self::default()
    }

    /// Cria o cliente legado apontando para outra base.
    pub fn with_server(server: impl Into<String>) -> Self {
        Self {
            server: server.into(),
        }
    }

    /// Executa uma chamada no formato legado. `dados` é um JSON com
    /// `credentials`, `body` e `action`.
    pub async fn request(&self, service: &str, dados: &str) -> Result<Json> {
        let payload: Value = serde_json::from_str(dados).map_err(|error| {
            Error::validation(format!("JSON inválido na requisição legada: {error}"))
                .with_source(error)
        })?;

        let credentials = payload
            .get("credentials")
            .filter(|value| value.is_object())
            .ok_or_else(|| Error::validation("invalid request, missing credentials"))?;
        let body = payload
            .get("body")
            .filter(|value| value.is_object())
            .ok_or_else(|| Error::validation("invalid request, missing body"))?
            .clone();

        let client = Arc::new(HttpClient::new(
            Config {
                base_url: Some(self.server.clone()),
                bearer_token: string_field(credentials, "BearerToken"),
                device_token: string_field(credentials, "DeviceToken"),
                retry: Some(RetryConfig::none()),
                ..Default::default()
            }
            .header("User-Agent", "APIBRASIL/RUST-SDK"),
        ));

        let mut path = service.to_string();
        if let Some(action) = payload.get("action").and_then(Value::as_str) {
            if !action.is_empty() {
                path.push('/');
                path.push_str(action);
            }
        }

        match client
            .post(&path, Some(body), &RequestOptions::default())
            .await
        {
            Ok(response) => Ok(response),
            // Comportamento legado: erros HTTP voltam decodificados.
            Err(error) => match (&error.response, error.status) {
                (Some(Value::Object(body)), _) => Ok(Value::Object(body.clone())),
                (_, Some(_)) => Ok(serde_json::json!({
                    "error": true,
                    "message": error.message,
                })),
                _ => Err(error),
            },
        }
    }

    /// Chama `/whatsapp/{action}`.
    pub async fn whatsapp(&self, dados: &str) -> Result<Json> {
        self.request("whatsapp", dados).await
    }

    /// Chama `/sms/{action}`.
    pub async fn sms(&self, dados: &str) -> Result<Json> {
        self.request("sms", dados).await
    }

    /// Chama `/cpf/dados/{action}`.
    pub async fn cpf(&self, dados: &str) -> Result<Json> {
        self.request("cpf/dados", dados).await
    }

    /// Chama `/dados/{action}`.
    pub async fn cnpj(&self, dados: &str) -> Result<Json> {
        self.request("dados", dados).await
    }
}

fn string_field(source: &Value, key: &str) -> Option<String> {
    Some(source.get(key)?.as_str()?.to_string())
}