defmodule GitGudWeb.ErrorMessage do
@moduledoc """
Turns an `{:error, reason}` into text fit to show whoever made the
request — an API reply, a flash — without dumping the term itself.
* a changeset → its field errors ("name has already been taken");
* a string → as is (it was written to be read);
* an atom → in words (`:not_found` → "not found");
* anything else (tuples, exceptions, structs) → "something went
wrong (ref …)", with the term logged under the same ref, so the
detail is one log search away for the operator and nowhere for
the caller.
"""
require Logger
@spec describe(term()) :: String.t()
def describe(%Ecto.Changeset{} = changeset) do
changeset
|> Ecto.Changeset.traverse_errors(fn {msg, opts} ->
Regex.replace(~r"%{(\w+)}", msg, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
|> flatten()
|> case do
[] -> "invalid input"
errors -> Enum.join(errors, "; ")
end
end
def describe(message) when is_binary(message), do: message
def describe(reason) when is_atom(reason) and reason not in [nil, true, false],
do: reason |> Atom.to_string() |> String.replace("_", " ")
def describe(reason) do
ref = 4 |> :crypto.strong_rand_bytes() |> Base.encode16(case: :lower)
Logger.error("error ref #{ref}: #{inspect(reason)}")
"something went wrong (ref #{ref})"
end
defp flatten(errors, prefix \\ nil) do
Enum.flat_map(errors, fn
{field, messages} when is_list(messages) and messages != [] and is_binary(hd(messages)) ->
Enum.map(messages, &"#{label(prefix, field)} #{&1}")
{field, nested} when is_map(nested) ->
flatten(nested, label(prefix, field))
{field, nested} when is_list(nested) ->
Enum.flat_map(nested, &flatten(&1, label(prefix, field)))
_ ->
[]
end)
end
defp label(nil, field), do: to_string(field)
defp label(prefix, field), do: "#{prefix}.#{field}"
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
2.0 KiB · text
History
b868907