lib/bsv/util.ex

defmodule BSV.Util do
  @moduledoc """
  Collection of shared helper functions, used frequently throughout the library.
  """

  @typedoc "Binary encoding format"
  @type encoding() :: :base64 | :hex

  @doc """
  Decodes the given binary data using the specified `t:BSV.Util.encoding/0`.

  Returns the result in an `:ok` / `:error` tuple pair.

  ## Examples

      iex> BSV.Util.decode("aGVsbG8gd29ybGQ=", :base64)
      {:ok, "hello world"}

      iex> BSV.Util.decode("68656c6c6f20776f726c64", :hex)
      {:ok, "hello world"}
  """
  @spec decode(binary(), encoding()) :: {:ok, binary()} | {:error, term()}
  def decode(data, encoding) do
    case do_decode(data, encoding) do
      {:ok, data} ->
        {:ok, data}
      :error ->
        {:error, {:invalid_encoding, encoding}}
    end
  end

  # Decodes the binary
  defp do_decode(data, :base64), do: Base.decode64(data)
  defp do_decode(data, :hex), do: Base.decode16(data, case: :mixed)
  defp do_decode(data, _), do: {:ok, data}

  @doc """
  Decodes the given binary data using the specified `t:BSV.Util.encoding/0`.

  As `decode/2` but returns the result or raises an exception.
  """
  @spec decode!(binary(), encoding()) :: binary()
  def decode!(data, encoding) do
    case decode(data, encoding) do
      {:ok, data} ->
        data
      {:error, error} ->
        raise BSV.DecodeError, error
    end
  end

  @doc """
  Encodes the given binary data using the specified `t:BSV.Util.encoding/0`.

  ## Examples

      iex> BSV.Util.encode("hello world", :base64)
      "aGVsbG8gd29ybGQ="

      iex> BSV.Util.encode("hello world", :hex)
      "68656c6c6f20776f726c64"
  """
  @spec encode(binary(), encoding()) :: binary()
  def encode(data, :base64), do: Base.encode64(data)
  def encode(data, :hex), do: Base.encode16(data, case: :lower)
  def encode(data, _), do: data

  @doc """
  Returns a binary containing the specified number of random bytes.
  """
  @spec rand_bytes(integer()) :: binary()
  def rand_bytes(bytes) when is_integer(bytes),
    do: :crypto.strong_rand_bytes(bytes)

  @doc """
  Reverses the bytes of the given binary data.

  ## Examples

      iex> BSV.Util.reverse_bin("abcdefg")
      "gfedcba"

      iex> BSV.Util.reverse_bin(<<1, 2, 3, 0>>)
      <<0, 3, 2, 1>>
  """
  @spec reverse_bin(binary()) :: binary()
  def reverse_bin(data) when is_binary(data) do
    data
    |> :binary.bin_to_list()
    |> Enum.reverse()
    |> :binary.list_to_bin()
  end

end