lib/types/cont_payload.ex

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

  alias Kadena.Types.{EnvData, PactTransactionHash, Proof, Rollback, Step}

  @behaviour Kadena.Types.Spec

  @type data :: EnvData.t() | nil
  @type pact_id :: PactTransactionHash.t()
  @type proof :: Proof.t() | nil
  @type rollback :: Rollback.t()
  @type step :: Step.t()
  @type str :: String.t()
  @type value :: data() | pact_id() | proof() | rollback() | step()
  @type validation :: {:ok, value()} | {:error, Keyword.t()}

  @type t :: %__MODULE__{
          data: data(),
          pact_id: pact_id(),
          proof: proof(),
          rollback: rollback(),
          step: step()
        }

  defstruct [:data, :pact_id, :proof, :rollback, :step]

  @impl true
  def new(args) do
    data = Keyword.get(args, :data)
    pact_id = Keyword.get(args, :pact_id)
    proof = Keyword.get(args, :proof)
    rollback = Keyword.get(args, :rollback)
    step = Keyword.get(args, :step)

    with {:ok, data} <- validate_data(data),
         {:ok, pact_id} <- validate_pact_id(pact_id),
         {:ok, proof} <- validate_proof(proof),
         {:ok, rollback} <- validate_rollback(rollback),
         {:ok, step} <- validate_step(step) do
      %__MODULE__{data: data, pact_id: pact_id, step: step, proof: proof, rollback: rollback}
    end
  end

  @spec validate_data(data :: map()) :: validation()
  defp validate_data(nil), do: {:ok, nil}
  defp validate_data(%EnvData{} = data), do: {:ok, data}

  defp validate_data(data) do
    case EnvData.new(data) do
      %EnvData{} = data -> {:ok, data}
      _error -> {:error, [data: :invalid]}
    end
  end

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

  @spec validate_proof(proof :: str()) :: validation()
  defp validate_proof(nil), do: {:ok, nil}

  defp validate_proof(proof) do
    case Proof.new(proof) do
      %Proof{} = proof -> {:ok, proof}
      _error -> {:error, [proof: :invalid]}
    end
  end

  @spec validate_rollback(rollback :: boolean()) :: validation()
  defp validate_rollback(rollback) do
    case Rollback.new(rollback) do
      %Rollback{} = rollback -> {:ok, rollback}
      _error -> {:error, [rollback: :invalid]}
    end
  end

  @spec validate_step(step :: integer()) :: validation()
  defp validate_step(step) do
    case Step.new(step) do
      %Step{} = step -> {:ok, step}
      _error -> {:error, [step: :invalid]}
    end
  end
end