lib/client.ex

defmodule ExIsbndb.Client do
  @moduledoc """
  The `ExIsbndb.Client` module is in charge of creating and sending
  requests to the ISBNdb API.
  """
  @base_url %{
    basic: "https://api2.isbndb.com/",
    premium: "https://api.premium.isbndb.com/",
    pro: "https://api.pro.isbndb.com/"
  }

  @doc """
  Builds and sends a request to the ISBNdb API.

  Accepts `:get` and `:post` methods.

  ## Examples

      iex> ExIsbndb.Client.request(:get, "author/{author_name}")
      {:ok, %Finch.Reponse{body: "json_string", headers: [...], status: 200}}

  """
  @spec request(:get | :post, binary(), map()) :: {:ok, Finch.Response.t()} | {:error, any()}
  def request(method, path, params \\ %{}) when is_map(params) and is_binary(path) do
    method
    |> build_request(path, params)
    |> Finch.request(IsbnFinch)
  end

  ##########
  ## PRIV ##
  ##########

  # Builds the Finch request
  defp build_request(:get, path, params),
    do: Finch.build(:get, build_url(path, params), headers())

  defp build_request(:post, path, params),
    do: Finch.build(:post, build_url(path), headers(), Jason.encode_to_iodata!(params))

  defp build_request(method, _path, _params),
    do:
      raise(
        ArgumentError,
        "unsupported method #{inspect(method)}. Supported methods `:get`, `:post`"
      )

  # Builds the URL with the query params if needed
  defp build_url(path), do: base_url() <> path
  defp build_url(path, params), do: base_url() <> path <> "?" <> URI.encode_query(params)

  # Returns the required HTTP headers
  defp headers, do: [{"Authorization", api_key()}]

  # Fetches the ISBNdb API key
  defp api_key do
    case Application.fetch_env!(:ex_isbndb, :api_key) do
      key when is_binary(key) ->
        key

      key ->
        raise KeyError,
              "unsupported API Key #{inspect(key)}. Must be a binary value."
    end
  end

  # Returns the base URL based on the ISBNdb plan
  defp base_url do
    plan = Application.fetch_env!(:ex_isbndb, :plan)

    case Map.fetch(@base_url, plan) do
      :error ->
        raise KeyError,
              "unsupported plan #{inspect(plan)}. Supported plans `:basic`, `:premium`, `:pro`"

      {:ok, url} ->
        url
    end
  end
end