lib/construct/exceptions.ex

defmodule Construct.Error do
  defexception [:message]
end

defmodule Construct.MakeError do
  defexception [:message]

  def exception(%{reason: reason, params: params}) when is_map(reason) do
    %__MODULE__{message: inspect(traverse_errors(reason, params))}
  end
  def exception(reason) when is_binary(reason) do
    %__MODULE__{message: reason}
  end
  def exception(reason) do
    %__MODULE__{message: inspect(reason)}
  end

  defp traverse_errors(reason, params) when is_list(params) do
    traverse_errors(reason, Enum.into(params, %{}))
  end
  defp traverse_errors(reason, params) do
    Enum.reduce(reason, %{}, fn
      ({field, error}, acc) when is_map(error) ->
        Map.put(acc, field, traverse_errors(error, get_params_field(params, field) || %{}))
      ({field, error}, acc) ->
        Map.put(acc, field, {error, get_params_field(params, field)})
    end)
  end

  defp get_params_field(list, field) when is_list(list) do
    Enum.map(list, &(get_params_field(&1, field)))
  end
  defp get_params_field(params, field) when is_map(params) do
    Map.get(params, field) || Map.get(params, Atom.to_string(field))
  end
end

defmodule Construct.DefinitionError do
  defexception [:message]
end