Splode-based error handling for MishkaGervaz.
Error Classes
:data- Data loading, query, and fetch errors:action- Action execution errors (destroy, update, etc.)
Usage
# Raise an error
raise MishkaGervaz.Errors.Data.LoadFailed, resource: MyResource, reason: :timeout
# Create error without raising
error = MishkaGervaz.Errors.Data.LoadFailed.exception(resource: MyResource, reason: :timeout)
# Convert any value into a Splode error (unrecognized values become `Errors.Unknown`)
MishkaGervaz.Errors.to_error(error)
# Format error for flash message
MishkaGervaz.Errors.format_flash_message(error)Translation
Every word these functions return goes through the Gettext backend
MishkaGervaz.Messages.gettext_backend/0 names, in the locale of the calling process:
- MishkaGervaz's own words and templates in the
"mishka_gervaz"domain. - An error's own message, such as Ash's
"is required", in the"errors"domain, withdngettextwhen its vars hold an integer:count. Its%{key}placeholders are filled from the error's vars after it is translated. - A field's name from the label the resource declares for it: its form field's
uilabel, else its table column's label. A field with neither is named by its humanized name in the"mishka_gervaz"domain.
A message whose last character ends a sentence (. ! ? … ؟ ۔ 。 and the like, before
any closing quote or bracket) is shown as written. Any other message is joined to its field's name
through the "%{field} %{message}" template.
A reason that is not a validation error is worded too: :not_found, and an
Ash.Error.Query.NotFound with no primary key, as "This record is no longer here."; an
Ash.Error.Forbidden by its policy's custom_message, else as "You are not allowed to do this."; any other exception by its own message.
Summary
Functions
Extracts a human-readable message from various error formats.
An error message about field, as a person reads it.
Formats an error into a human-readable flash message.
Translates an error's own message in the "errors" domain and fills its %{key} placeholders
from vars.
Traverses errors, calling fun for each leaf error, and returns a nested
map of results grouped by each error's path.
Raises an error if the result is an error, otherwise returns the result
Types
@type class() :: %{ :__struct__ => class_module(), :__exception__ => true, :errors => [t()], :class => error_class(), :bread_crumbs => [String.t()], :vars => Keyword.t(), :stacktrace => Splode.Stacktrace.t() | nil, :context => map(), optional(atom()) => any() }
@type class_module() ::
MishkaGervaz.Errors.Action | MishkaGervaz.Errors.Data | Splode.Error.Unknown
@type error_class() :: :action | :data | :unknown
@type t() :: %{ :__struct__ => module(), :__exception__ => true, :class => error_class(), :bread_crumbs => [String.t()], :vars => Keyword.t(), :stacktrace => Splode.Stacktrace.t() | nil, :context => map(), optional(atom()) => any() }
Functions
Extracts a human-readable message from various error formats.
resource is the resource whose declared labels name the error's field. An Ash error AshPhoenix
can show on a form (AshPhoenix.FormData.Error) is read the way the form reads it; one the form
shows on no field is read by its own message. An error class holding several errors, such as
Ash.Error.Invalid, joins the message of each, a repeated message once.
Examples
iex> MishkaGervaz.Errors.extract_error_message(%{message: "Invalid email"})
"Invalid email"
iex> MishkaGervaz.Errors.extract_error_message(%{field: :email, message: "is invalid"})
"Email is invalid"
iex> MishkaGervaz.Errors.extract_error_message(%{field: :tag_id, message: "That tag is archived."})
"That tag is archived."
iex> MishkaGervaz.Errors.extract_error_message(%{field: :title, message: "must be at least %{min}", vars: [min: 3]})
"Title must be at least 3"
@spec field_message( String.t() | nil, keyword() | map() | nil, atom() | String.t() | nil, module() | nil ) :: String.t()
An error message about field, as a person reads it.
message is translated with translate_error/2. A translated message that ends a sentence is
returned as written; any other is joined to the field's name through the "%{field} %{message}"
template. field is named by the label resource declares for it, else by its humanized name.
A nil field, or AshPhoenix's :_form for the whole form, returns the message alone.
Formats an error into a human-readable flash message.
Handles MishkaGervaz errors, Ash errors, and generic errors. resource names the fields of an
error that has no resource of its own; an Action.Failed or Data.LoadFailed uses its own
:resource when resource is nil.
An Action.Failed names its action by its :label (a string or a zero-arity function), else by
its humanized :action.
Examples
iex> error = MishkaGervaz.Errors.Action.Failed.exception(action: :archive, reason: "forbidden")
iex> MishkaGervaz.Errors.format_flash_message(error)
"Archive failed: forbidden"
iex> error = MishkaGervaz.Errors.Action.Failed.exception(action: :archive, label: "Delete", reason: "forbidden")
iex> MishkaGervaz.Errors.format_flash_message(error)
"Delete failed: forbidden"
Translates an error's own message in the "errors" domain and fills its %{key} placeholders
from vars.
Uses dngettext when vars holds an integer :count. A list var is joined with ", ", and a
value String.Chars cannot print is inspected. A message with a %{key} placeholder vars does
not fill is returned as written.
Examples
iex> MishkaGervaz.Errors.translate_error("must be at least %{min}", min: 3)
"must be at least 3"
Traverses errors, calling fun for each leaf error, and returns a nested
map of results grouped by each error's path.
See Splode.traverse_errors/2 for full documentation.
Example
iex> Elixir.MishkaGervaz.Errors.traverse_errors(error, fn error ->
...> Exception.message(error)
...> end)
%{name: ["name is required"]}
Raises an error if the result is an error, otherwise returns the result
Alternatively, you can use the defsplode macro, which does this automatically.
Options
:error_opts- Options to pass toto_error/2when converting the returned error:unknown_error_opts- Options to pass to the unknown error if the function returns only:error. not necessary if your function always returns{:error, error}.
Examples
def function(arg) do
case do_something(arg) do
:success -> :ok
{:success, result} -> {:ok, result}
{:error, error} -> {:error, error}
endend
def function!(arg) do
YourErrors.unwrap!(function(arg))end