lib/metabase.ex

defmodule Metabase do
  @moduledoc """
  Client used to make HTTP requests.

  ## Making Requests

  Requests can be made using the `send/2` function. This function accepts a
  `Metabase.RequestOperation` struct as the first argument and a keyword list of
  configuration options as the second argument.

  ### Example

      iex> %Metabase.RequestOperation{}
      ...> |> Map.put(:body, username: "person@mymetabase.com", password: "fakepassword")
      ...> |> Map.put(:method, :post)
      ...> |> Map.put(:path, "/session")
      ...> |> Metabase.send(host: "mymetabase.com/api")
      {:ok, %Metabase.Response{}}

  ## Configuration

  The `send/2` function takes a keyword list of configuration options as the
  second argument. These provide the client with additional details needed to
  make a request and various other options for how the client should process the
  request and how it should behave.

  ### Options

  * `:client` - HTTP client adapter used to make the request. Defaults to
    `Metabase.HTTP.Hackney`.
  * `:client_opts` - Configuration options passed to the client adapter
  * `:headers` - HTTP headers used when making a request
  * `:host` - Hostname used when making a request. This field is required.
  * `:json_codec` - Module used to encode and decode JSON. Defaults to `Jason`.
  * `:port` - HTTP port used when making a request
  * `:protocol` - HTTP protocol used when making a request
  * `:retry` - Module implementing a request retry strategy. Disabled when set
    to `false`. Defaults to `false`.
  * `:retry_opts` - Options used to control the behavior of the retry module

  """

  alias Metabase.{HTTP, Opts, Request, RequestOperation, Response}

  @type headers_t ::
          [{String.t(), String.t()}]

  @type method_t ::
          :delete | :get | :head | :patch | :post | :put

  @type response_t ::
          {:ok, Response.t()} | {:error, Response.t() | any}

  @type status_code_t ::
          pos_integer

  @doc """
  Send an HTTP request.
  """
  @spec send(
          RequestOperation.t(),
          keyword
        ) :: Response.t()
  def send(operation, old_opts) do
    opts = Opts.new(old_opts)

    request = Request.new(operation, opts)

    case opts.client.send(request, opts) do
      {:ok, %HTTP.Response{status_code: status_code} = response}
      when status_code >= 400 ->
        {:error, Response.new(response, opts)}

      {:ok, %HTTP.Response{status_code: status_code} = response}
      when status_code >= 200 ->
        {:ok, Response.new(response, opts)}

      otherwise ->
        otherwise
    end
  end
end