lib/types/meta_data.ex

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

  alias Kadena.Types.ChainID

  @behaviour Kadena.Types.Spec

  @type creation_time :: number()
  @type ttl :: number()
  @type gas_limit :: number()
  @type gas_price :: number()
  @type sender :: String.t()
  @type chain_id :: ChainID.t()
  @type chain_id_arg :: String.t()
  @type values :: creation_time() | ttl() | gas_limit() | gas_price() | sender() | chain_id()
  @type validation :: {:ok, values()} | {:error, Keyword.t()}

  @type t :: %__MODULE__{
          creation_time: creation_time(),
          ttl: ttl(),
          gas_limit: gas_limit(),
          gas_price: gas_price(),
          sender: sender(),
          chain_id: chain_id()
        }

  defstruct [:creation_time, :ttl, :gas_limit, :gas_price, :sender, :chain_id]

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

  def new(args) when is_list(args) do
    creation_time = Keyword.get(args, :creation_time, 0)
    ttl = Keyword.get(args, :ttl, 0)
    gas_limit = Keyword.get(args, :gas_limit, 0)
    gas_price = Keyword.get(args, :gas_price, 0)
    sender = Keyword.get(args, :sender, "")
    chain_id = Keyword.get(args, :chain_id, "0")

    with {:ok, creation_time} <- validate_creation_time(creation_time),
         {:ok, ttl} <- validate_number(:ttl, ttl),
         {:ok, gas_limit} <- validate_number(:gas_limit, gas_limit),
         {:ok, gas_price} <- validate_number(:gas_price, gas_price),
         {:ok, sender} <- validate_sender(sender),
         {:ok, chain_id} <- validate_chain_id(chain_id) do
      %__MODULE__{
        creation_time: creation_time,
        ttl: ttl,
        gas_limit: gas_limit,
        gas_price: gas_price,
        sender: sender,
        chain_id: chain_id
      }
    end
  end

  def new(args) when is_map(args) do
    creation_time = Map.get(args, "creationTime", 0)
    ttl = Map.get(args, "ttl", 0)
    gas_limit = Map.get(args, "gasLimit", 0)
    gas_price = Map.get(args, "gasPrice", 0)
    sender = Map.get(args, "sender", "")
    chain_id = Map.get(args, "chainId", "0")

    with {:ok, creation_time} <- validate_creation_time(creation_time),
         {:ok, ttl} <- validate_number(:ttl, ttl),
         {:ok, gas_limit} <- validate_number(:gas_limit, gas_limit),
         {:ok, gas_price} <- validate_number(:gas_price, gas_price),
         {:ok, sender} <- validate_sender(sender),
         {:ok, chain_id} <- validate_chain_id(chain_id) do
      %__MODULE__{
        creation_time: creation_time,
        ttl: ttl,
        gas_limit: gas_limit,
        gas_price: gas_price,
        sender: sender,
        chain_id: chain_id
      }
    end
  end

  def new(_args), do: {:error, [args: :invalid]}

  @spec validate_creation_time(creation_time :: creation_time()) :: validation()
  defp validate_creation_time(creation_time) when is_number(creation_time),
    do: {:ok, creation_time}

  defp validate_creation_time(_creation_time), do: {:error, [creation_time: :invalid]}

  @spec validate_number(field :: atom(), value :: number()) :: validation()
  defp validate_number(_field, value) when is_number(value),
    do: {:ok, value}

  defp validate_number(field, _value), do: {:error, [{field, :invalid}]}

  @spec validate_sender(sender :: sender()) :: validation()
  defp validate_sender(sender) when is_binary(sender),
    do: {:ok, sender}

  defp validate_sender(_sender), do: {:error, [sender: :invalid]}

  @spec validate_chain_id(chain_id :: chain_id_arg()) :: validation()
  defp validate_chain_id(id) do
    case ChainID.new(id) do
      %ChainID{} = chain_id -> {:ok, chain_id}
      _error -> {:error, [chain_id: :invalid]}
    end
  end
end