lib/glific_web/schema/middleware/changeset_errors.ex

defmodule GlificWeb.Schema.Middleware.ChangesetErrors do
  @moduledoc """
  Implementing middleware functions to transform errors from Ecto Changeset into a format
  consumable and displayable to the API user. This version is specifically for mutations.
  """

  @behaviour Absinthe.Middleware

  @doc """
  This is the main middleware callback.

  It receives an %Absinthe.Resolution{} struct and it needs to return an %Absinthe.Resolution{} struct.
  The second argument will be whatever value was passed to the middleware call that setup the middleware.
  """
  @spec call(Absinthe.Resolution.t(), term()) :: Absinthe.Resolution.t()
  def call(res, _) do
    l = Map.get(res, :errors)

    if length(l) == 2 do
      [h | t] = l
      %{res | value: %{errors: [%{key: h, message: t}]}, errors: []}
    else
      with %{errors: [%Ecto.Changeset{} = changeset]} <- res do
        %{res | value: %{errors: transform_errors(changeset)}, errors: []}
      end
    end
  end

  @spec transform_errors(Ecto.Changeset.t()) :: Keyword.t()
  defp transform_errors(changeset) do
    changeset
    |> Ecto.Changeset.traverse_errors(&format_error/1)
    |> Enum.map(fn
      {key, value} ->
        key_msg = key |> Atom.to_string() |> String.capitalize()
        %{key: key, message: "#{key_msg}: #{value}"}
    end)
  end

  @spec format_error(Ecto.Changeset.error()) :: String.t()
  defp format_error({msg, opts}) do
    Enum.reduce(opts, msg, fn {key, value}, acc ->
      String.replace(acc, "%{#{key}}", to_string(value))
    end)
  end
end