Resourceful.Error (Resourceful v0.1.6)
View SourceErrors in Resourceful
follow a few conventions. This module contains
functions to help work with those conventions. Client-facing errors are
loosely inspired by and should be easily converted to JSON:API-style
errors, however they should also be
suitable when JSON:API isn't used at all.
Error Structure
All errors returned in Resourceful
are expected to be two element tuples
where the first element is :error
. This is a common Elixir and Erlang
convention, but it's strict for the purposes of this library. Resourceful
generates and expects one of two kinds of errors:
Basic Errors
Basic errors are always a two element tuple where the second element is an atom that should related to specific kind of error returned. These are meant for situations where context is either easily inferred, unnecessary, or obvious in some other manner.
Example: {:error, :basic_err}
Basic errors that require context are often transformed into contextual errors by higher level functions as they have context that lower level functions do not.
Contextual Errors
Contextual errors are always a two element tuple where the second element is another two element tuple in which the first element is an atom and the second element is a map containing contextual information such as user input, data being validated, and/or the source of the error. The map's keys must be atoms.
Example: {:error, {:context_err, %{input: "XYZ", source: ["email_address"]}}
Errors outside of this format are not expected to work with these functions.
Sources
While not strictly required by contextual errors the :source
key is used to
indicate an element in a complex data structure that is responsible for a
particular error. It must be a list of atoms, strings, or integers used to
navigate a tree of data. Non-nested values should still be in the form of a
list as prefixing sources is common. It's common for functions generating
errors to be ignorant of their full context and higher level functions to
prepend errors with their current location.
Other Common Conventions
There are a few other keys that appear in errors regularly:
:detail
: A human friendly description about the error. If there is information related to resolving the error, it belongs here.:input
: A text representation of the actual input given by the client. It should be as close as possible to the original. (In general,IO.inspect/1
is used.):key
: Not to be confused with:source
, a:key
key should always be present when a lookup of data is done by some sort of key and there is a failure.:value
: Not to be confused with:input
, a:value
key
Note: all of these keys have convenience functions as it is a very common convention to create a new error with one of these keys or add one to an existing error.
Error Types
Both basic and contextual errors will contain an atom describing the specific
type of error. These should be thought of as error codes and should be unique
to the kinds of errors. For example, :attribute_not_found
means that in some
context, an attribute for a given resource doesn't exist. This could be an
attempt to filter or sort. Either way, the error type remains the same. In
both contexts, this error means the same thing.
Contextual errors of a particular type should always contain at least some
expected keys in their context maps. For example, :attribute_not_found
should always contain a :name
and may contain other information. More is
usually better when it comes to errors.
Keys should remain consistent in their use. :invalid_filter_operator
, for
example, should always contain an :attribute
key implying that the failure
was on a valid attribute whereas :attribute_not_found
contains a :key
key
implying it was never resolved to an actual attribute.
Summary
Functions
Mostly a convenience function to use instead of list/1
with the option to
auto source errors as well. Additionally it will take a non-collection value
and convert it to a list.
Recursively checks arbitrary data structures for any basic or contextual errors.
Transverses an arbitrary data structure that may contain errors and prepends
:source
data given the error's position in the data structure using either
an index from a list or a key from a map.
Extracts the context map from an error.
Deletes key
from an error's context map if present.
Converts errors from an Ecto Changeset into Resourceful errors. The type
is
inferred from the :validation
key as Resourceful tends to use longer names.
Rather than relying on separate input params, the :input
is inserted from
data
, and :source
is also inferred.
Many error types should, at a minimum, have an associated :title
. If there
are regular context values, it should also include a :detail
value as well.
Both of these keys provide extra information about the nature of the error
and can help the client understand the particulars of the provided context.
While it might not be readily obvious what :key
means in an error, if it is
used in :detail
it will help the client understand the significance.
Transforms an arbitrary data structure that may contain errors into a single, flat list of those errors. Non-error values are removed. Collections are checked recursively.
Replaces context bindings in a message with atom keys in a context map.
Recursively transforms arbitrary data structures containing :ok
tuples with
just values. Values which are not in :ok
tuples are left untouched.
Checks an arbitrary data structure for errors and returns either the errors or valid data.
Adds or prepends source context to an error. A common pattern when dealing with sources in nested data structures being transversed recursively is for the child structure to have no knowledge of the parent structure. Once the child errors are resolved the parent can then prepend its location in the structure to the child's errors.
Adds a context map to an error if it lacks one, converting a basic error to a
contextual error. It may also take a single atom to prevent error generating
code from having to constantly wrap errors in an :error
tuple.
Adds the specified context as an error's context. If the error already has a context map the new context is merged.
Adds a context map to an error if it lacks on and then puts the key and value into that map.
Convenience function to create or modify an existing error with :input
context.
Convenience function to create or modify an existing error with :key
context.
Adds source context to an error and replaces :source
if present.
Types
Functions
Mostly a convenience function to use instead of list/1
with the option to
auto source errors as well. Additionally it will take a non-collection value
and convert it to a list.
Returns a list of errors.
Recursively checks arbitrary data structures for any basic or contextual errors.
Transverses an arbitrary data structure that may contain errors and prepends
:source
data given the error's position in the data structure using either
an index from a list or a key from a map.
Note: This will not work for keyword lists. In order to infer source information they must first be converted to maps.
Returns input data structure with errors modified to contain :source
in
their context.
Extracts the context map from an error.
Returns a context map.
Deletes key
from an error's context map if present.
Returns an error tuple.
@spec from_changeset(%Ecto.Changeset{ action: term(), changes: term(), constraints: term(), data: term(), empty_values: term(), errors: term(), filters: term(), params: term(), prepare: term(), repo: term(), repo_opts: term(), required: term(), types: term(), valid?: term(), validations: term() }) :: [contextual()]
Converts errors from an Ecto Changeset into Resourceful errors. The type
is
inferred from the :validation
key as Resourceful tends to use longer names.
Rather than relying on separate input params, the :input
is inserted from
data
, and :source
is also inferred.
@spec humanize( t() | [t()], keyword() ) :: contextual() | [contextual()]
Many error types should, at a minimum, have an associated :title
. If there
are regular context values, it should also include a :detail
value as well.
Both of these keys provide extra information about the nature of the error
and can help the client understand the particulars of the provided context.
While it might not be readily obvious what :key
means in an error, if it is
used in :detail
it will help the client understand the significance.
Unlike the error's type itself--which realistically should serve as an error code of sorts--the title should should be more human readable and able to be localized, although it should be consistent. Similarly, detail should be able to be localized although it can change depending on the specifics of the error or values in the context map.
This function handles injecting default :title
and :detail
items into the
context map if they are available for an error type and replacing context-
related bindings in messages. (See message_with_context/2
for details.)
In the future, this is also where localization should happen.
Transforms an arbitrary data structure that may contain errors into a single, flat list of those errors. Non-error values are removed. Collections are checked recursively.
Maps are given special treatment in that their values are checked but their keys are discarded.
This format is meant to keep reading errors fairly simple and consistent at the edge. Clients can rely on reading a single list of errors regardless of whether transversing nested validation failures or handling more simple single fault situations.
This function is also designed with another convention in mind: mixing successes and failures in a single payload. A design goal of error use in this library is to wait until as late as possible to return errors. That way, a single request can return the totality of its failure to the client. This way, many different paths can be evaluated and if there are any errors along the way, those errors can be returned in full.
Replaces context bindings in a message with atom keys in a context map.
A message of "Invalid input %{input}." would have
%{input}replaced with the value in the context map of
:input`.
Recursively transforms arbitrary data structures containing :ok
tuples with
just values. Values which are not in :ok
tuples are left untouched.
It does not check for errors. If errors are included, they will remain in the
structure untouched. This function is designed to work on error free data and
is unlikely to be used on its own but rather with or_ok/1
.
Keyword lists where :ok
may be included with other keys won't be returned
as probably intended. Keep this limitation in mind.
Returns the input data structure with all instances of {:ok, value}
replaced
with value
.
Checks an arbitrary data structure for errors and returns either the errors or valid data.
See all/1
, any?/1
, and ok_value/1
for specific details as this function
combines the three into a common pattern. Return the errors if there are any
or the validated data.
Returns either a list of errors wrapped in an :error
tuple or valid data
wrapped in an :ok
tuple.
@spec prepend_source(or_type() | [or_type()], any()) :: contextual() | [contextual()]
Adds or prepends source context to an error. A common pattern when dealing with sources in nested data structures being transversed recursively is for the child structure to have no knowledge of the parent structure. Once the child errors are resolved the parent can then prepend its location in the structure to the child's errors.
@spec with_context(or_type()) :: contextual()
Adds a context map to an error if it lacks one, converting a basic error to a
contextual error. It may also take a single atom to prevent error generating
code from having to constantly wrap errors in an :error
tuple.
@spec with_context(or_type(), map()) :: contextual()
Adds the specified context as an error's context. If the error already has a context map the new context is merged.
@spec with_context(or_type(), atom(), any()) :: contextual()
Adds a context map to an error if it lacks on and then puts the key and value into that map.
@spec with_input(or_type(), any()) :: contextual()
Convenience function to create or modify an existing error with :input
context.
@spec with_key(or_type(), any()) :: contextual()
Convenience function to create or modify an existing error with :key
context.
@spec with_source(or_type(), any(), map()) :: contextual()
Adds source context to an error and replaces :source
if present.