lib/tx_build/sc_error.ex

defmodule Stellar.TxBuild.SCError do
  @moduledoc """
  `ScErrorCode` struct definition.
  """
  alias StellarBase.XDR.{SCError, SCErrorType, SCErrorCode, UInt32}

  @behaviour Stellar.TxBuild.XDR

  @type code ::
          :arith_domain
          | :index_bounds
          | :invalid_input
          | :missing_value
          | :existing_value
          | :exceeded_limit
          | :invalid_action
          | :internal_error
          | :unexpected_type
          | :unexpected_size
  @type type ::
          :contract
          | :wasm_vm
          | :context
          | :storage
          | :object
          | :crypto
          | :events
          | :budget
          | :code
          | :auth

  @type t :: %__MODULE__{type: type(), code: code()}

  defstruct [:type, :code]

  @types %{
    contract: :SCE_CONTRACT,
    wasm_vm: :SCE_WASM_VM,
    context: :SCE_CONTEXT,
    storage: :SCE_STORAGE,
    object: :SCE_OBJECT,
    crypto: :SCE_CRYPTO,
    events: :SCE_EVENTS,
    budget: :SCE_BUDGET,
    value: :SCE_VALUE,
    auth: :SCE_AUTH
  }

  @codes %{
    arith_domain: :SCEC_ARITH_DOMAIN,
    index_bounds: :SCEC_INDEX_BOUNDS,
    invalid_input: :SCEC_INVALID_INPUT,
    missing_value: :SCEC_MISSING_VALUE,
    existing_value: :SCEC_EXISTING_VALUE,
    exceeded_limit: :SCEC_EXCEEDED_LIMIT,
    invalid_action: :SCEC_INVALID_ACTION,
    internal_error: :SCEC_INTERNAL_ERROR,
    unexpected_type: :SCEC_UNEXPECTED_TYPE,
    unexpected_size: :SCEC_UNEXPECTED_SIZE
  }

  @impl true
  def new(args, opts \\ [])

  def new([{type, code}], _opts)
      when is_map_key(@types, type) and (is_map_key(@codes, code) or is_integer(code)) do
    %__MODULE__{type: type, code: code}
  end

  def new(_args, _opts), do: {:error, :invalid_sc_error}

  @impl true
  def to_xdr(%__MODULE__{type: :contract = type, code: code}) when is_integer(code) do
    with {:ok, type} <- retrieve_xdr_type(type) do
      type = SCErrorType.new(type)

      code
      |> UInt32.new()
      |> SCError.new(type)
    end
  end

  def to_xdr(%__MODULE__{type: type, code: code}) do
    with {:ok, type} <- retrieve_xdr_type(type),
         {:ok, code} <- retrieve_xdr_code(code) do
      type = SCErrorType.new(type)

      code
      |> SCErrorCode.new()
      |> SCError.new(type)
    end
  end

  def to_xdr(_struct), do: {:error, :invalid_struct}

  defp retrieve_xdr_type(type), do: {:ok, Map.get(@types, type)}
  defp retrieve_xdr_code(code), do: {:ok, Map.get(@codes, code)}
end