lib/github/error.ex

defmodule GitHub.Error do
  @moduledoc """
  Exception struct used for communicating errors from the client

  > #### Note {:.info}
  >
  > Functions in this module is unlikely to be used directly by applications. Instead, they are
  > useful for plugins. See `GitHub.Plugin` for more information.

  This error covers errors generated by the client (for example, HTTP connection errors) as well as
  errors returned from the GitHub API (for example, Not Found errors).

  ## Fields

    * `code` (integer): Status code of the API response, if a response was received.

    * `message` (string): Human-readable message describing the error.

    * `operation` (`t:Operation.t/0`): Operation at the time of the error.

    * `source` (term): Cause of the error. This could be an operation, an API error response, or
      something else.

    * `stacktrace` (`t:Exception.stacktrace/0`): Stacktrace from the time of the error.

    * `step` (plugin): Plugin active at the time of the error (expressed as a tuple containing the
      module and function).

  Users of the library can match on the information in the `code` and `source` fields to extract
  additional information.
  """

  alias GitHub.Operation

  @typedoc "GitHub API client error"
  @type t :: %__MODULE__{
          code: integer | nil,
          message: String.t(),
          operation: Operation.t(),
          source: term,
          stacktrace: Exception.stacktrace(),
          step: {module, atom}
        }

  @derive {Inspect, except: [:operation, :stacktrace]}
  defexception [:code, :message, :operation, :source, :stacktrace, :step]

  @doc """
  Create a new error struct with the given fields

  The current stacktrace is automatically filled in to the resulting error. Callers should specify
  the status `code` (if available), a `message`, the original `operation`, the `source` of the
  error, and which `step` or plugin is currently active.
  """
  @spec new(keyword) :: t
  def new(opts) do
    {:current_stacktrace, stack} = Process.info(self(), :current_stacktrace)
    # Drop `Process.info/2` and `new/1`.
    stacktrace = Enum.drop(stack, 2)

    struct!(%__MODULE__{stacktrace: stacktrace}, opts)
  end
end