lib/flow.ex

defmodule PipeHelpers.Flow do
  def new() do
    {:ok, %{}}
  end

  def start(name, fun) do
    start(name, %{}, fun)
  end

  def start(name, init, fun) do
    then_ok({:ok, init}, name, fun)
  end

  def then_ok({:ok, state}, name, fun) do
    if Map.has_key?(state, name) do
      raise ArgumentError, message: "name #{name} is already used in flow"
    end

    fun
    |> Function.info()
    |> Keyword.get(:arity)
    |> case do
      0 -> fun.()
      1 -> fun.(state)
      _ -> raise "then_ok function arity can only be 0 or 1"
    end
    |> case do
      {:ok, val} ->
        if is_nil(name) do
          state
        else
          Map.put(state, name, val)
        end
        |> PipeHelpers.ok()

      {:error, e} ->
        {:error, state, name, e}

      e ->
        {:error, state, name, e}
    end
  end

  def then_ok(result, _, _fun), do: result

  def tap_ok(result, fun) do
    then_ok(result, nil, fun)
    result
  end

  def map_ok({:ok, state}, fun) do
    fun
    |> Function.info()
    |> Keyword.get(:arity)
    |> case do
      0 -> fun.()
      1 -> fun.(state)
      _ -> raise "then_ok function arity can only be 0 or 1"
    end
  end

  def map_ok(result, _fun), do: result

  def finish({:ok, state}, key) do
    if Map.has_key?(state, key) do
      {:ok, Map.get(state, key)}
    else
      {:error, :key_undefined}
    end
  end

  def finish({:error, _state, k, err}, _key) do
    {:error, {k, err}}
  end

  def finish(result, _key), do: result
end