lib/kayrock/error_code.ex

defmodule Kayrock.ErrorCode do
  @moduledoc """
  Utility for converting Kafka error codes to/from atom names

  Note the actual error code values are determined by `:kpro_schema.c/1`.
  Currently covering the range -1 to 71.

  See https://kafka.apache.org/protocol#protocol_error_codes
  """

  defmodule InvalidErrorCodeException do
    @moduledoc """
    Raised if `Kayrock.ErrorCode.code_to_atom!/1` is called with a code that
    does not correspond to a known Kafka error code.
    """
    defexception [:message]

    @impl true
    def exception(code) do
      %__MODULE__{message: "Invalid error code: #{inspect(code)}"}
    end
  end

  defmodule InvalidAtomException do
    @moduledoc """
    Raised if `Kayrock.ErrorCode.atom_to_code!/1` is called with an atom that
    does not correspond to a known Kafka error code.
    """
    defexception [:message]

    @impl true
    def exception(atom) do
      %__MODULE__{message: "Invalid error atom: #{inspect(atom)}"}
    end
  end

  alias Kayrock.KafkaSchemaMetadata

  @known_codes -1..71
  case Code.ensure_compiled(KafkaSchemaMetadata) do
    {:module, _} ->
      @atom_to_code Enum.into(@known_codes, %{}, fn c ->
                      {KafkaSchemaMetadata.error_code_to_error(c), c}
                    end)
  end

  @typedoc """
  A numeric Kafka error code
  """
  @type error_code :: integer

  @typedoc """
  An erlang/elixir atom representation of an error code
  """
  @type error_atom :: atom

  require Logger

  @doc """
  Converts an error code to an atom

  An unknown code results in a return of `:unknown`
  """
  @spec code_to_atom(error_code) :: error_atom
  def code_to_atom(code) when code in @known_codes do
    KafkaSchemaMetadata.error_code_to_error(code)
  end

  def code_to_atom(code) do
    Logger.warning("Unknown error code #{inspect(code)}")
    :unknown
  end

  @doc """
  Converts an error code to an atom, raising an
  `Kayrock.ErrorCode.InvalidErrorCodeException` if an unknown error code is supplied
  """
  @spec code_to_atom!(error_code) :: error_atom
  def code_to_atom!(code) do
    case code_to_atom(code) do
      :unknown -> raise InvalidErrorCodeException, code
      v -> v
    end
  end

  @doc """
  Converts an atom to a numeric error code

  Returns nil if an unknown atom is supplied
  """
  @spec atom_to_code(error_atom) :: error_code | nil
  def atom_to_code(atom) do
    Map.get(@atom_to_code, atom)
  end

  @doc """
  Converts an atom to a numeric error code, raising an
  `Kayrock.ErrorCode.InvalidAtomException` if an unknown atom is supplied
  """
  @spec atom_to_code!(error_atom) :: error_code
  def atom_to_code!(atom) do
    case atom_to_code(atom) do
      nil -> raise InvalidAtomException, atom
      v -> v
    end
  end

  ######################################################################
  # convenience functions
  @spec unknown_topic() :: error_code
  def unknown_topic, do: atom_to_code!(:unknown_topic_or_partition)
end