lib/github/issues.ex

# ┌───────────────────────────────────────────────────────────┐
# │ Inspired by the book "Programming Elixir" by Dave Thomas. │
# └───────────────────────────────────────────────────────────┘
defmodule GitHub.Issues do
  @moduledoc """
  Fetches a list of issues from a GitHub project.
  """

  use PersistConfig

  alias __MODULE__.{CLI, Log}

  @url_template get_env(:url_template)

  @type issue :: map

  @doc """
  Fetches issues from a GitHub `project` of a given `user`.

  Returns a tuple of either `{:ok, [issue]}` or `{:error, text}`.

  ## Parameters

    - `user`         - GitHub user
    - `project`      - GitHub project
    - `url_template` - URL template (EEx string)

  ## Examples

      iex> alias GitHub.Issues
      iex> {:ok, issues} = Issues.fetch("opendrops", "passport")
      iex> Enum.all?(issues, &is_map/1) and length(issues) > 0
      true
  """
  @spec fetch(CLI.user(), CLI.project(), String.t()) ::
          {:ok, [issue]} | {:error, String.t()}
  def fetch(user, project, url_template \\ @url_template) do
    url = url(user, project, url_template)
    :ok = Log.info(:fetching, {user, project, url, __ENV__})

    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        {:ok, :jsx.decode(body, [:return_maps])}

      {:ok, %HTTPoison.Response{status_code: code}} ->
        {:error, status(code)}

      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, error(reason)}
    end
  end

  ## Private functions

  @spec status(pos_integer) :: String.t()
  defp status(301), do: "status code 301 ⇒ Moved Permanently"
  defp status(302), do: "status code 302 ⇒ Found"
  defp status(403), do: "status code 403 ⇒ Forbidden"
  defp status(404), do: "status code 404 ⇒ Not Found"
  defp status(code), do: "status code #{code}"

  @spec error(term) :: String.t()
  defp error(reason), do: "reason => #{inspect(reason)}"

  # @doc """
  # Returns a URL based on `user`, `project` and `url_template`.

  # ## Parameters

  #   - `user`         - user
  #   - `project`      - project
  #   - `url_template` - URL template (EEx string)

  # ## Examples

  #     iex> alias GitHub.Issues
  #     iex> url_template = "api.github.com/repos/<%=user%>/<%=project%>/issues"
  #     iex> Issues.url("opendrops", "passport", url_template)
  #     "api.github.com/repos/opendrops/passport/issues"

  #     iex> alias GitHub.Issues
  #     iex> url_template = "elixir-lang.org/<%=project%>/<%=user%>/wow"
  #     iex> Issues.url("José", "Elixir", url_template)
  #     "elixir-lang.org/Elixir/José/wow"

  #     iex> alias GitHub.Issues
  #     iex> url_template = "elixir-lang.org/<project>/<user>/wow"
  #     iex> Issues.url("José", "Elixir", url_template)
  #     "elixir-lang.org/<project>/<user>/wow"
  # """
  @spec url(CLI.user(), CLI.project(), String.t()) :: String.t()
  defp url(user, project, url_template) do
    EEx.eval_string(url_template, user: user, project: project)
  end
end