Error handling on a list: How to propagate {:error, _} tuples?

Today I found myself writing the following piece of code source in context:

    def from_list(other_users_json_list) do
      other_users_json_list
      |> reduce_errors(&from_json_hash/1)
    end

    defp reduce_errors(enumerable, function) do
      enumerable
      |> Enum.reduce({:ok, []}, fn
        {:error, error}, _ -> {:error, error}
        {:ok, list}, elem ->
          case function.(elem) do
            {:ok, elem} -> [elem | list]
            {:error, error} -> {:error, error}
          end
      end)
    end

It tries to turn a list of maps that was parsed before from JSON into a list of structs, returning either {:ok, list_of_structs} or {:error, some_validation_error}.

It should stop parsing as soon as the first error is found. However, the only way I know of that propagates an ok/error-tuple through a list of changes is the above, which feels like an awful lot of manual labour and not very idiomatic Elixir-code.

But I cannot be the only one that has faced this type of problem: Are there other idioms of handling these situations?

If I’m understanding correctly, I think Enum.reduce_while would fit nicely here. Caution: code has not been executed

Enum.reduce_while(enumerable, {:ok, []}, fn elem, {:ok, acc} ->
  case function.(elem) do
    {:ok, new_elem} -> {:cont, {:ok, [new_elem | acc]}}
    {:error, _} = err -> {:halt, err}
  end
end)
8 Likes