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 core utils.ex
Raw

lib/api_brasil/core/utils.ex

defmodule ApiBrasil.Core.Utils do
@moduledoc """
Utilidades compartilhadas: montagem de URL, query string, normalização de
headers e conversão de bodies.
"""
alias ApiBrasil.Core.JSON
@doc """
Junta a base da API com o caminho, sem barras duplicadas.
iex> ApiBrasil.Core.Utils.join_url("https://gateway.apibrasil.io/api/v2/", "/whatsapp/sendText")
"https://gateway.apibrasil.io/api/v2/whatsapp/sendText"
"""
@spec join_url(String.t(), String.t()) :: String.t()
def join_url(base_url, path) do
base = String.trim_trailing(base_url, "/")
suffix = String.trim_leading(path || "", "/")
if suffix == "", do: base, else: base <> "/" <> suffix
end
@doc """
Monta a query string a partir de um mapa/keyword, ignorando valores nulos.
As chaves saem ordenadas, o que torna a URL determinística. Devolve `""`
quando não há nada a enviar.
iex> ApiBrasil.Core.Utils.build_query_string(%{"search" => "cep", "page" => 2})
"?page=2&search=cep"
"""
@spec build_query_string(map() | keyword() | nil) :: String.t()
def build_query_string(nil), do: ""
def build_query_string([]), do: ""
def build_query_string(query) when is_map(query) and map_size(query) == 0, do: ""
def build_query_string(query) when is_map(query) or is_list(query) do
pairs =
query
|> Enum.map(fn {key, value} -> {to_string(key), value} end)
|> Enum.reject(fn {_key, value} -> is_nil(value) end)
|> Enum.sort_by(fn {key, _value} -> key end)
|> Enum.map(fn {key, value} ->
URI.encode_www_form(key) <> "=" <> URI.encode_www_form(query_value(value))
end)
if pairs == [], do: "", else: "?" <> Enum.join(pairs, "&")
end
@doc "Codifica um segmento de caminho de URL (mantém `/`)."
@spec encode_path(String.t()) :: String.t()
def encode_path(value) do
URI.encode(to_string(value), &keep_in_path?/1)
end
defp keep_in_path?(char) do
char in ?A..?Z or char in ?a..?z or char in ?0..?9 or char in ~c"-_.~/"
end
@doc """
Normaliza o corpo de uma requisição.
Aceita mapas, keyword lists, structs `ApiBrasil.Consulta`, `nil` e `[]`
devolve um mapa ou `nil` quando não há corpo.
"""
@spec to_body(term()) :: map() | list() | nil
def to_body(nil), do: nil
def to_body([]), do: nil
def to_body(%ApiBrasil.Consulta{} = consulta), do: ApiBrasil.Consulta.to_map(consulta)
def to_body(%_struct{} = struct), do: Map.from_struct(struct) |> stringify_keys()
def to_body(body) when is_map(body) and map_size(body) == 0, do: nil
def to_body(body) when is_map(body), do: stringify_keys(body)
def to_body(body) when is_list(body), do: list_to_body(body)
def to_body(body), do: body
@doc """
Converte as chaves de um mapa (um nível) em strings — aceita átomos e
strings misturados.
"""
@spec stringify_keys(map()) :: map()
def stringify_keys(map) when is_map(map) do
Map.new(map, fn {key, value} -> {to_string(key), value} end)
end
@doc """
Normaliza headers para um mapa `%{"nome" => "valor"}`, preservando o nome
original mas removendo duplicatas por comparação sem diferenciar caixa.
"""
@spec normalize_headers(map() | keyword() | nil) :: map()
def normalize_headers(nil), do: %{}
def normalize_headers(headers) when is_map(headers) or is_list(headers) do
Map.new(headers, fn {key, value} -> {to_string(key), to_string(value)} end)
end
@doc """
Mescla dois conjuntos de headers sem diferenciar maiúsculas de minúsculas —
os de `override` vencem, inclusive quando só a caixa difere.
"""
@spec merge_headers(map(), map()) :: map()
def merge_headers(base, override) do
downcased = Map.new(override, fn {key, value} -> {String.downcase(key), value} end)
base
|> Enum.reject(fn {key, _value} -> Map.has_key?(downcased, String.downcase(key)) end)
|> Map.new()
|> Map.merge(override)
end
@doc """
Normaliza a resposta em um mapa JSON.
Respostas vazias viram `%{}`; respostas que não são objetos (listas, texto)
são embrulhadas em `%{"data" => ...}`.
"""
@spec to_json_object(term()) :: map()
def to_json_object(nil), do: %{}
def to_json_object(value) when is_map(value), do: value
def to_json_object(value), do: %{"data" => value}
@doc "Devolve `nil` para strings vazias — credenciais vazias contam como ausentes."
@spec presence(term()) :: term()
def presence(nil), do: nil
def presence(""), do: nil
def presence(value), do: value
defp list_to_body(body) do
if Keyword.keyword?(body) do
body |> Map.new() |> stringify_keys()
else
body
end
end
defp query_value(value) when is_binary(value), do: value
defp query_value(value) when is_atom(value), do: to_string(value)
defp query_value(value) when is_integer(value) or is_float(value), do: to_string(value)
defp query_value(value), do: JSON.encode!(value)
end