lib/types/pact_decimal.ex

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

  alias Decimal

  @behaviour Kadena.Types.Spec

  @type decimal :: Decimal.t()
  @type value :: String.t()
  @type errors :: Keyword.t()
  @type validation :: {:ok, decimal()} | {:error, errors()}

  @type t :: %__MODULE__{value: value(), raw_value: decimal()}

  defstruct [:value, :raw_value]

  @lower_decimal_range -9_007_199_254_740_991
  @upper_decimal_range 9_007_199_254_740_991

  @impl true
  def new(value) do
    with {:ok, decimal} <- parse_decimal(value),
         {:ok, decimal} <- validate_decimal_range(decimal) do
      %__MODULE__{value: value, raw_value: decimal}
    end
  end

  @spec parse_decimal(value :: value()) :: validation()
  defp parse_decimal(value) when is_binary(value) do
    case Decimal.cast(value) do
      {:ok, decimal} -> {:ok, decimal}
      :error -> {:error, [value: :invalid]}
    end
  end

  defp parse_decimal(_value), do: {:error, [value: :invalid]}

  @spec validate_decimal_range(decimal :: decimal()) :: validation()
  defp validate_decimal_range(decimal) do
    if Decimal.gt?(decimal, @upper_decimal_range) || Decimal.lt?(decimal, @lower_decimal_range),
      do: {:ok, decimal},
      else: {:error, [value: :not_in_range]}
  end
end