lib/geocoder/http_client/hackney.ex

defmodule Geocoder.HttpClient.Hackney do
  @moduledoc """
  Hackney client. See Hackney for documentation for options that can be passed
  in the http_client_opts
  """
  @behaviour Geocoder.HttpClient

  if Code.ensure_loaded?(:hackney) do
    def request(%{method: method, url: url, query_params: params} = request, config \\ []) do
      http_client_opts = config[:http_client_opts]
      json_codec = config[:json_codec]
      opts = [:with_body | http_client_opts] ++ [ibrowse: [headers_as_is: true], params: params]
      headers = request[:headers] || []

      final_url = build_url(url, params)

      case :hackney.request(method, final_url, headers, "", opts) do
        {:ok, status, headers} ->
          {:ok, %{status_code: status, headers: headers}}

        {:ok, status, headers, body} ->
          {:ok, %{status_code: status, headers: headers, body: json_codec.decode!(body)}}

        {:error, reason} ->
          {:error, %{reason: reason}}
      end
    end

    defp build_url(url, query_params) do
      if Enum.empty?(query_params) do
        url
      else
        url <> "?" <> :hackney_url.qs(Map.to_list(query_params))
      end
    end
  end
end