lib/flamel/result.ex

defmodule Flamel.Result do
  @moduledoc """
  Some helper functions for dealing with result tuples
  """

  import Flamel.Wrap, only: [ok: 1]

  @doc """
  Applies a function to the value of an `{:ok, value}`. The return value of the function is wrapped in an `{:ok, value}` tuple.
  If the tuple passed to map is an `{:error, reason}` tuple then the function is not applied and the `{:error, reason}` tuple is returned.

  ## Examples

      iex> Flamel.Result.map({:ok, []}, fn v -> ["test" | v] end)
      {:ok, ["test"]}

      iex> Flamel.Result.map({:error, "message"}, fn v -> ["test" | v] end)
      {:error, "message"}
  """
  @spec map(tuple(), fun()) :: tuple()
  def map({:ok, value}, func), do: value |> func.() |> ok()
  def map({:ok, value, second_value}, func), do: value |> func.(second_value) |> ok()
  def map(value, _func), do: value

  @doc """
  Essentially has the same behavior as `Flamel.Result.map/2` but the return value of the function is not automatically wrapped in an `{:ok, value}` tuple. You should return either a `{:ok, value}` tuple or `{:error, reason}` tuple from the function.
  `Flamel.Result.then/2` should be used when the function can produce an error.

  ## Examples

      iex> Flamel.Result.then({:ok, []}, fn v -> {:ok, ["test" | v]} end)
      {:ok, ["test"]}

      iex> Flamel.Result.then({:ok, []}, fn _v -> {:error, :bad_arg} end)
      {:error, :bad_arg}

      iex> Flamel.Result.then({:error, "message"}, fn v -> ["test" | v] end)
      {:error, "message"}
  """
  @spec then(tuple(), fun()) :: tuple()
  def then({:ok, value}, func), do: func.(value)
  def then({:ok, value, second_value}, func), do: func.(value, second_value)
  def then(value, _func), do: value

  @doc """
  Returns true if the value is an `:ok`, `{:ok, value}`, `{:ok, value, value}`

  ## Examples

      iex> Flamel.Result.ok?({:ok, []})
      true

      iex> Flamel.Result.ok?({:error, "message"})
      false
  """
  def ok?(:ok), do: true
  def ok?({:ok, _value}), do: true
  def ok?({:ok, _value, _second_value}), do: true
  def ok?(_), do: false

  @doc """
  Returns true if the value is an `:error`, `{:error, value}`, `{:error, value, value}`

  ## Examples

      iex> Flamel.Result.error?({:error, "error message"})
      true

      iex> Flamel.Result.error?({:ok, "message"})
      false
  """
  def error?(:error), do: true
  def error?({:error, _value}), do: true
  def error?({:error, _value, _second_value}), do: true
  def error?(_), do: false

  @doc """
  Takes an {:ok, value} tuple and returns the value

  ## Examples

      iex> Flamel.Result.ok!({:ok, []})
      []

      iex> Flamel.Result.ok!({:error, "message"})
      ** (ArgumentError) {:error, "message"} is not an :ok tuple
  """
  def ok!({:ok, value}) do
    value
  end

  def ok!(value) do
    raise ArgumentError, message: "#{inspect(value)} is not an :ok tuple"
  end

  @doc """
  Takes an {:ok, value} tuple and returns the value

  ## Examples

      iex> Flamel.Result.error!({:ok, []})
      ** (ArgumentError) {:ok, []} is not an :error tuple

      iex> Flamel.Result.error!({:error, "message"})
      "message"
  """
  def error!({:error, value}) do
    value
  end

  def error!(value) do
    raise ArgumentError, message: "#{inspect(value)} is not an :error tuple"
  end

  @doc """
  Takes an {:ok, value} tuple and returns the value or nil

  ## Examples

      iex> Flamel.Result.ok_or_nil({:ok, []})
      []

      iex> Flamel.Result.ok_or_nil({:error, "message"})
      nil
  """
  def ok_or_nil({:ok, value}) do
    value
  end

  def ok_or_nil(_) do
    nil
  end

  @doc """
  Takes an {:error, value} tuple and returns the value or nil

  ## Examples

      iex> Flamel.Result.error_or_nil({:ok, []})
      nil

      iex> Flamel.Result.error_or_nil({:error, "message"})
      "message"
  """
  def error_or_nil({:error, value}) do
    value
  end

  def error_or_nil(_) do
    nil
  end
end