lib/aws/http_client.ex

defmodule AWS.HTTPClient do
  require Logger

  @moduledoc """
  Specifies the behaviour of a HTTP Client.

  You can switch the default HTTP client which uses hackney underneath
  by defining a different implementation by setting the `:http_client`
  configuration in AWS.Client:

      client = %AWS.Client{http_client: {MyHttpClient, []}}
      AWS.SNS.publish(client, "My message")

  """

  @doc """
  Executes a HTTP request. Either returns {:ok, map} or {:error, reason}.

  - `body` is already parsed into iodata. It may be in either JSON or XML format.
  - `headers` already contains required headers such as Authorization. See AWS.Request.sign_v4 for more details.
  """
  @callback request(
              method :: atom(),
              url :: binary(),
              body :: iodata(),
              headers :: list(),
              options :: keyword()
            ) ::
              {:ok, %{status_code: integer(), headers: [{binary(), binary()}], body: binary()}}
              | {:error, term()}

  @hackney_pool_name :aws_pool

  def request(method, url, body, headers, options) do
    ensure_hackney_running!()

    options = [:with_body | options] |> Keyword.put_new(:pool, @hackney_pool_name)

    case :hackney.request(method, url, headers, body, options) do
      {:ok, status_code, response_headers, body} ->
        {:ok, %{status_code: status_code, headers: response_headers, body: body}}

      {:ok, status_code, response_headers} ->
        {:ok, %{status_code: status_code, headers: response_headers, body: ""}}

      {:error, _error} = error ->
        error
    end
  end

  defp ensure_hackney_running!() do
    unless Code.ensure_loaded?(:hackney) do
      Logger.error("""
      Could not find hackney dependency.

      Please add :hackney to your dependencies:

          {:hackney, "~> 1.16"}

      Or provide your own #{__MODULE__} implementation:

          %AWS.Client{http_client: {MyCustomHTTPClient, []}}
      """)

      raise "missing hackney dependency"
    end

    {:ok, _apps} = Application.ensure_all_started(:hackney)
  end
end