Skip to main content

lib/format.ex

defmodule Noizu.Github.Format do
  @moduledoc """
  Hand-maintained, curated views over generated structs.

  The generated structs under `lib/api/structs/` are full, spec-faithful and
  permissive — great for fidelity, noisy for display. This module layers the
  curated `:basic` projections that the original hand-written client exposed via
  per-struct `format/2` helpers, without touching (or being wiped by) the
  generator.

  Unknown shapes pass through unchanged, so `format/2` is always safe to call.

      Noizu.Github.Format.format(issue)              # => %{id: 1, title: ..., ...}
      Noizu.Github.Format.format(collection, :basic) # => [%{...}, ...]
  """

  alias Noizu.Github.{Issue, Label, SimpleUser, NullableSimpleUser, Collection}

  @type format :: :basic

  @doc "Project a generated struct (or list/collection of them) into a curated view."
  @spec format(term, format) :: term
  def format(value, format \\ :basic)

  def format(nil, _format), do: nil

  def format(list, format) when is_list(list), do: Enum.map(list, &format(&1, format))

  # Collections unwrap to their items.
  def format(%Collection{items: items}, format), do: format(items, format)

  def format(%{__struct__: mod, items: items}, format)
      when mod in [
             Collection.Issue,
             Collection.Label,
             Collection.SimpleUser
           ],
      do: format(items, format)

  def format(%Issue{} = this, :basic) do
    %{
      id: this.number,
      internal_id: this.id,
      url: this.html_url,
      labels: format(this.labels, :basic),
      title: this.title,
      body: this.body,
      state: this.state,
      reactions: this.reactions,
      user: format(this.user, :basic),
      assignee: format(this.assignee, :basic),
      assignees: format(this.assignees, :basic),
      milestone: this.milestone,
      comments: this.comments,
      created_at: this.created_at,
      updated_at: this.updated_at,
      closed_at: this.closed_at
    }
  end

  def format(%SimpleUser{} = this, :basic), do: user_basic(this)
  def format(%NullableSimpleUser{} = this, :basic), do: user_basic(this)

  def format(%Label{} = this, :basic), do: %{name: this.name, color: this.color}

  # `issue.labels` decode to raw maps (the schema's `oneOf[string, object]`).
  def format(%{name: name} = label, :basic) when is_map(label) and not is_struct(label) do
    %{name: name, color: Map.get(label, :color)}
  end

  def format(other, _format), do: other

  defp user_basic(user) do
    %{login: user.login, avatar_url: user.avatar_url, url: user.html_url}
  end
end