lib/types/command_result.ex

defmodule Kadena.Types.CommandResult do
  @moduledoc """
  `CommandResult` struct definition.
  """

  alias Kadena.Types.{
    Base64Url,
    ChainwebResponseMetaData,
    OptionalPactEventsList,
    PactEventsList,
    PactExec,
    PactResult
  }

  @behaviour Kadena.Types.Spec

  @type str :: String.t()
  @type req_key :: Base64Url.t()
  @type tx_id :: number() | nil
  @type result :: PactResult.t()
  @type gas :: number()
  @type logs :: String.t() | nil
  @type continuation :: PactExec.t()
  @type meta_data :: ChainwebResponseMetaData.t() | nil
  @type events :: OptionalPactEventsList.t()
  @type events_arg :: PactEventsList.t() | list() | nil
  @type value ::
          str()
          | req_key()
          | tx_id()
          | result()
          | gas()
          | logs()
          | continuation()
          | meta_data()
          | events()
  @type validation :: {:ok, value()} | {:error, Keyword.t()}

  @type t :: %__MODULE__{
          req_key: req_key(),
          tx_id: tx_id(),
          result: result(),
          gas: gas(),
          logs: logs(),
          continuation: continuation(),
          meta_data: meta_data(),
          events: events()
        }

  defstruct [:req_key, :tx_id, :result, :gas, :logs, :continuation, :meta_data, :events]

  @impl true
  def new(args) do
    req_key = Keyword.get(args, :req_key)
    tx_id = Keyword.get(args, :tx_id)
    result = Keyword.get(args, :result)
    gas = Keyword.get(args, :gas)
    logs = Keyword.get(args, :logs)
    continuation = Keyword.get(args, :continuation)
    meta_data = Keyword.get(args, :meta_data)
    events = Keyword.get(args, :events)

    with {:ok, req_key} <- validate_req_key(req_key),
         {:ok, tx_id} <- validate_number(:tx_id, tx_id),
         {:ok, result} <- validate_result(result),
         {:ok, gas} <- validate_number(:gas, gas),
         {:ok, logs} <- validate_logs(logs),
         {:ok, continuation} <- validate_continuation(continuation),
         {:ok, meta_data} <- validate_meta_data(meta_data),
         {:ok, events} <- validate_events(events) do
      %__MODULE__{
        req_key: req_key,
        tx_id: tx_id,
        result: result,
        gas: gas,
        logs: logs,
        continuation: continuation,
        meta_data: meta_data,
        events: events
      }
    end
  end

  @spec validate_req_key(req_key :: str()) :: validation()
  defp validate_req_key(req_key) do
    case Base64Url.new(req_key) do
      %Base64Url{} = req_key -> {:ok, req_key}
      _error -> {:error, [req_key: :invalid]}
    end
  end

  @spec validate_number(field :: atom(), value :: number()) :: validation()
  defp validate_number(:tx_id, nil), do: {:ok, nil}
  defp validate_number(_field, value) when is_number(value), do: {:ok, value}
  defp validate_number(field, _tx_id), do: {:error, [{field, :invalid}]}

  @spec validate_result(result :: result()) :: validation()
  defp validate_result(%PactResult{} = result), do: {:ok, result}

  defp validate_result(result) when is_list(result) do
    case PactResult.new(result) do
      %PactResult{} = result -> {:ok, result}
      {:error, reason} -> {:error, [result: :invalid] ++ reason}
    end
  end

  defp validate_result(_result), do: {:error, [result: :invalid]}

  @spec validate_logs(logs :: logs()) :: validation()
  defp validate_logs(nil), do: {:ok, nil}
  defp validate_logs(logs) when is_binary(logs), do: {:ok, logs}
  defp validate_logs(_logs), do: {:error, [logs: :invalid]}

  @spec validate_continuation(continuation :: continuation()) :: validation()
  defp validate_continuation(%PactExec{} = continuation), do: {:ok, continuation}

  defp validate_continuation(continuation) when is_list(continuation) do
    case PactExec.new(continuation) do
      %PactExec{} = continuation -> {:ok, continuation}
      {:error, reason} -> {:error, [continuation: :invalid] ++ reason}
    end
  end

  defp validate_continuation(_continuation), do: {:error, [continuation: :invalid]}

  @spec validate_meta_data(meta_data :: meta_data()) :: validation()
  defp validate_meta_data(nil), do: {:ok, nil}
  defp validate_meta_data(%ChainwebResponseMetaData{} = meta_data), do: {:ok, meta_data}

  defp validate_meta_data(meta_data) when is_list(meta_data) do
    case ChainwebResponseMetaData.new(meta_data) do
      %ChainwebResponseMetaData{} = meta_data -> {:ok, meta_data}
      {:error, reason} -> {:error, [meta_data: :invalid] ++ reason}
    end
  end

  defp validate_meta_data(_result), do: {:error, [meta_data: :invalid]}

  @spec validate_events(events :: events_arg()) :: validation()
  defp validate_events(nil), do: {:ok, OptionalPactEventsList.new()}
  defp validate_events(%PactEventsList{} = events), do: {:ok, OptionalPactEventsList.new(events)}

  defp validate_events(events) when is_list(events) do
    with %PactEventsList{} = pact_events <- PactEventsList.new(events),
         %OptionalPactEventsList{} = optional_events <- OptionalPactEventsList.new(pact_events) do
      {:ok, optional_events}
    else
      {:error, reason} -> {:error, [events: :invalid] ++ reason}
    end
  end

  defp validate_events(_events), do: {:error, [events: :invalid]}
end