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