Packages

SDK oficial Elixir da plataforma APIBrasil: WhatsApp, SMS, consultas de CPF/CNPJ, veiculos, CEP, correios, pagamentos PIX/boleto e mais.

Current section

Files

Jump to
apibrasil lib api_brasil legacy.ex
Raw

lib/api_brasil/legacy.ex

defmodule ApiBrasil.Legacy do
@moduledoc """
Interface legada da SDK, mantida por compatibilidade com o formato de
chamada das primeiras SDKs da plataforma (`credentials`, `body` e `action`
em uma única string JSON).
Prefira o cliente `ApiBrasil`, que cobre toda a plataforma com funções
dedicadas, erros tipados, retry e hooks.
legacy = ApiBrasil.Legacy.new()
dados = ~s({
"action": "sendText",
"credentials": {
"DeviceToken": "SEU_DEVICE_TOKEN",
"BearerToken": "SEU_BEARER_TOKEN"
},
"body": {"number": "5511999999999", "text": "Hello World for Elixir"}
})
{:ok, resposta} = ApiBrasil.Legacy.whatsapp(legacy, dados)
Assim como nas versões anteriores, respostas de erro da API voltam
**decodificadas** em `{:ok, mapa}` em vez de virarem `{:error, _}` — só
falhas de rede, de validação do payload legado e de tempo limite chegam
como `{:error, %ApiBrasil.Core.Error{}}`.
"""
alias ApiBrasil.Core.{Error, JSON, Retry, Service}
@default_server "https://gateway.apibrasil.io/api/v2/"
@user_agent "APIBRASIL/ELIXIR-SDK"
@typedoc "Cliente legado: apenas a base da API."
@type t :: %__MODULE__{server: String.t()}
@typedoc "Resultado de uma chamada legada."
@type result :: {:ok, map()} | {:error, Error.t()}
@doc """
Cliente legado.
- `:server` — base da API. Padrão: `#{@default_server}`.
"""
defstruct server: @default_server
@doc """
Cria o cliente legado. Sem argumento, aponta para o gateway padrão.
ApiBrasil.Legacy.new()
ApiBrasil.Legacy.new("https://gateway.apibrasil.io/api/v2/")
"""
@spec new(String.t()) :: t()
def new(server \\ @default_server), do: %__MODULE__{server: server}
@doc "Base usada quando o cliente legado é criado sem `server`."
@spec default_server() :: String.t()
def default_server, do: @default_server
@doc """
Executa uma chamada no formato legado: `POST {server}/{servico}/{action}`.
`dados` é uma **string JSON** com:
- `credentials` — objeto com `BearerToken` e/ou `DeviceToken`;
- `body` — objeto enviado no corpo da requisição;
- `action` — opcional; quando presente, vira o último segmento da rota.
JSON inválido, `credentials` ausente ou `body` ausente devolvem
`{:error, %ApiBrasil.Core.Error{kind: :validation}}`. Erros da API voltam
decodificados em `{:ok, mapa}`.
ApiBrasil.Legacy.request(legacy, "whatsapp", dados)
"""
@spec request(t(), String.t(), String.t(), keyword()) :: result()
def request(%__MODULE__{} = legacy, service, dados, opts \\ []) do
with {:ok, payload} <- decode(dados),
{:ok, credentials} <- fetch_object(payload, "credentials"),
{:ok, body} <- fetch_object(payload, "body") do
legacy
|> client(credentials)
|> Service.post(path(service, payload), body, opts)
|> legacy_response()
end
end
@doc "Como `request/4`, mas levanta `ApiBrasil.Core.Error` em caso de falha."
@spec request!(t(), String.t(), String.t(), keyword()) :: map()
def request!(%__MODULE__{} = legacy, service, dados, opts \\ []) do
Service.unwrap!(request(legacy, service, dados, opts))
end
@doc "Chama `/whatsapp/{action}`."
@spec whatsapp(t(), String.t(), keyword()) :: result()
def whatsapp(%__MODULE__{} = legacy, dados, opts \\ []) do
request(legacy, "whatsapp", dados, opts)
end
@doc "Como `whatsapp/3`, mas levanta `ApiBrasil.Core.Error` em caso de falha."
@spec whatsapp!(t(), String.t(), keyword()) :: map()
def whatsapp!(%__MODULE__{} = legacy, dados, opts \\ []) do
Service.unwrap!(whatsapp(legacy, dados, opts))
end
@doc "Chama `/sms/{action}`."
@spec sms(t(), String.t(), keyword()) :: result()
def sms(%__MODULE__{} = legacy, dados, opts \\ []) do
request(legacy, "sms", dados, opts)
end
@doc "Como `sms/3`, mas levanta `ApiBrasil.Core.Error` em caso de falha."
@spec sms!(t(), String.t(), keyword()) :: map()
def sms!(%__MODULE__{} = legacy, dados, opts \\ []) do
Service.unwrap!(sms(legacy, dados, opts))
end
@doc "Chama `/cpf/dados/{action}`."
@spec cpf(t(), String.t(), keyword()) :: result()
def cpf(%__MODULE__{} = legacy, dados, opts \\ []) do
request(legacy, "cpf/dados", dados, opts)
end
@doc "Como `cpf/3`, mas levanta `ApiBrasil.Core.Error` em caso de falha."
@spec cpf!(t(), String.t(), keyword()) :: map()
def cpf!(%__MODULE__{} = legacy, dados, opts \\ []) do
Service.unwrap!(cpf(legacy, dados, opts))
end
@doc "Chama `/dados/{action}`."
@spec cnpj(t(), String.t(), keyword()) :: result()
def cnpj(%__MODULE__{} = legacy, dados, opts \\ []) do
request(legacy, "dados", dados, opts)
end
@doc "Como `cnpj/3`, mas levanta `ApiBrasil.Core.Error` em caso de falha."
@spec cnpj!(t(), String.t(), keyword()) :: map()
def cnpj!(%__MODULE__{} = legacy, dados, opts \\ []) do
Service.unwrap!(cnpj(legacy, dados, opts))
end
defp decode(dados) when is_binary(dados) do
case JSON.decode(dados) do
{:ok, payload} ->
{:ok, payload}
{:error, reason} ->
{:error,
Error.validation("JSON inválido na requisição legada: #{describe(reason)}",
reason: reason
)}
end
end
defp decode(_dados) do
{:error, Error.validation("A requisição legada espera os dados em uma string JSON.")}
end
defp describe(reason) when is_exception(reason), do: Exception.message(reason)
defp describe(reason), do: inspect(reason)
defp fetch_object(payload, key) when is_map(payload) do
case Map.get(payload, key) do
%{} = value -> {:ok, value}
_other -> {:error, Error.validation("invalid request, missing #{key}")}
end
end
defp fetch_object(_payload, key) do
{:error, Error.validation("invalid request, missing #{key}")}
end
defp client(%__MODULE__{server: server}, credentials) do
ApiBrasil.new(
base_url: server,
bearer_token: string_field(credentials, "BearerToken"),
device_token: string_field(credentials, "DeviceToken"),
retry: Retry.none(),
headers: %{"User-Agent" => @user_agent}
)
end
defp string_field(source, key) do
case Map.get(source, key) do
value when is_binary(value) -> value
_other -> nil
end
end
defp path(service, payload) do
case Map.get(payload, "action") do
action when is_binary(action) and action != "" -> "#{service}/#{action}"
_other -> to_string(service)
end
end
# Comportamento legado: erros HTTP voltam decodificados em `{:ok, mapa}`.
defp legacy_response({:ok, response}), do: {:ok, response}
defp legacy_response({:error, %Error{response: response}}) when is_map(response) do
{:ok, response}
end
defp legacy_response({:error, %Error{status: status, message: message}})
when is_integer(status) do
{:ok, %{"error" => true, "message" => message}}
end
defp legacy_response({:error, %Error{} = error}), do: {:error, error}
end