Current section
Files
Jump to
Current section
Files
lib/ash/error.ex
defmodule Ash.Error do
@moduledoc false
@type error_class() :: :invalid | :authorization | :framework | :unknown
# We use these error classes also to choose a single error
# to raise when multiple errors have occured. We raise them
# sorted by their error classes
@error_classes [
:forbidden,
:invalid,
:framework,
:unknown
]
alias Ash.Error.{Forbidden, Framework, Invalid, Unknown}
@error_modules [
forbidden: Forbidden,
invalid: Invalid,
framework: Framework,
unknown: Unknown
]
@error_class_indices @error_classes |> Enum.with_index() |> Enum.into(%{})
def ash_error?(value) do
!!impl_for(value)
end
def to_ash_error(values) when is_list(values) do
values =
Enum.map(values, fn value ->
if ash_error?(value) do
value
else
Unknown.exception(error: values)
end
end)
choose_error(values)
end
def to_ash_error(value) do
to_ash_error([value])
end
def choose_error(errors) do
[error | other_errors] =
Enum.sort_by(errors, fn error ->
# the second element here sorts errors that are already parent errors
{Map.get(@error_class_indices, error.class),
@error_modules[error.class] != error.__struct__}
end)
parent_error_module = @error_modules[error.class]
if parent_error_module == error.__struct__ do
%{error | errors: (error.errors || []) ++ other_errors}
else
parent_error_module.exception(errors: errors)
end
end
def error_messages(errors, custom_message \\ nil) do
generic_message =
errors
|> List.wrap()
|> Enum.group_by(& &1.class)
|> Enum.sort_by(fn {group, _} -> Map.get(@error_class_indices, group) end)
|> Enum.map_join("\n\n", fn {class, class_errors} ->
header = header(class) <> "\n\n"
header <> Enum.map_join(class_errors, "\n", &"* #{Exception.message(&1)}")
end)
if custom_message do
custom =
custom_message
|> List.wrap()
|> Enum.map_join("\n", &"* #{&1}")
"\n\n" <> custom <> generic_message
else
generic_message
end
end
def error_descriptions(errors) do
errors
|> Enum.group_by(& &1.class)
|> Enum.sort_by(fn {group, _} -> Map.get(@error_class_indices, group) end)
|> Enum.map_join("\n\n", fn {class, class_errors} ->
header = header(class) <> "\n\n"
header <> Enum.map_join(class_errors, "\n", &"* #{Ash.Error.message(&1)}")
end)
end
defp header(:invalid), do: "Input Invalid"
defp header(:forbidden), do: "Forbidden"
defp header(:framework), do: "Framework Error"
defp header(:unknown), do: "Unknown Error"
defmacro __using__(_) do
quote do
import Ash.Error, only: [def_ash_error: 1, def_ash_error: 2]
end
end
defmacro def_ash_error(fields, opts \\ []) do
quote do
defexception unquote(fields) ++ [path: [], class: unquote(opts)[:class]]
@impl Exception
defdelegate message(error), to: Ash.Error
end
end
defdelegate id(error), to: Ash.ErrorKind
defdelegate code(error), to: Ash.ErrorKind
defdelegate message(error), to: Ash.ErrorKind
defdelegate impl_for(error), to: Ash.ErrorKind
end
defprotocol Ash.ErrorKind do
@moduledoc false
@spec id(t()) :: String.t()
def id(error)
@spec code(t()) :: String.t()
def code(error)
@spec message(t()) :: String.t()
def message(error)
end