Current section
Files
Jump to
Current section
Files
lib/api_brasil/core/transport/finch.ex
defmodule ApiBrasil.Core.Transport.Finch do
@moduledoc """
Transporte opcional sobre o [Finch](https://hex.pm/packages/finch) — pool
de conexões, HTTP/2 e telemetria.
Adicione o Finch ao projeto, suba o pool na sua árvore de supervisão e
informe o nome ao criar o cliente:
# mix.exs
{:finch, "~> 0.16"}
# application.ex
children = [{Finch, name: MinhaApp.Finch}]
# cliente
ApiBrasil.new(
transport: {ApiBrasil.Core.Transport.Finch, name: MinhaApp.Finch}
)
## Opções
- `:name` — nome do pool Finch (obrigatório);
- `:receive_timeout` — sobrescreve o tempo limite da requisição;
- `:request_opts` — opções repassadas a `Finch.request/3`.
"""
@behaviour ApiBrasil.Core.Transport
alias ApiBrasil.Core.{Error, Transport}
alias ApiBrasil.Core.Transport.{Request, Response}
@impl true
def request(%Request{} = request, opts) do
with :ok <- ensure_available(),
{:ok, name} <- fetch_name(opts) do
headers = Enum.map(request.headers, fn {key, value} -> {key, value} end)
finch_request =
apply(Finch, :build, [request.method, request.url, headers, request.body])
request_opts =
opts
|> Keyword.get(:request_opts, [])
|> Keyword.put_new(:receive_timeout, opts[:receive_timeout] || request.timeout || 30_000)
case apply(Finch, :request, [finch_request, name, request_opts]) do
{:ok, %{status: status, headers: headers, body: body}} ->
{:ok, response(status, headers, body, request.response_type)}
{:error, reason} ->
{:error, error_for(reason, request)}
end
end
end
defp response(status, headers, body, response_type) do
body = body || ""
%Response{
status: status,
headers: Map.new(headers, fn {key, value} -> {String.downcase(key), value} end),
data: Transport.decode_body(body, response_type),
body: body
}
end
defp fetch_name(opts) do
case Keyword.get(opts, :name) do
nil ->
{:error,
Error.validation(
"Informe o nome do pool Finch: " <>
"transport: {ApiBrasil.Core.Transport.Finch, name: MinhaApp.Finch}"
)}
name ->
{:ok, name}
end
end
defp ensure_available do
if Code.ensure_loaded?(Finch) do
:ok
else
{:error,
Error.validation(
"O Finch não está disponível. Adicione {:finch, \"~> 0.16\"} ao seu projeto " <>
"ou use o transporte padrão (ApiBrasil.Core.Transport.Httpc)."
)}
end
end
defp error_for(%{__exception__: true, reason: :timeout} = reason, request) do
Error.timeout("Tempo limite excedido em #{target(request)}: #{inspect(reason)}",
reason: reason
)
end
defp error_for(:timeout = reason, request) do
Error.timeout("Tempo limite excedido em #{target(request)}.", reason: reason)
end
defp error_for(reason, request) do
Error.network("Falha de rede em #{target(request)}: #{inspect(reason)}", reason: reason)
end
defp target(%Request{method: method, url: url}) do
"#{Transport.method_to_string(method)} #{url}"
end
end