Packages
A serialization library implementing the jsonapi.org 1.0 spec suitable for use building JSON APIs in Phoenix and any other Plug based framework or app.
Current section
Files
Jump to
Current section
Files
lib/ja_serializer/ecto_error_serializer.ex
defmodule JaSerializer.EctoErrorSerializer do
alias JaSerializer.Formatter.Utils
@moduledoc """
The EctoErrorSerializer is used to transform Ecto changeset errors to JSON API standard
error objects.
If a changeset is past in without optional error members then the object returned will
only contain: source, title, and detail.
```
%{"errors" => [
%{
source: %{pointer: "/data/attributes/monies"},
title: "must be more than 10",
detail: "Monies must be more than 10"
}
]
}
```
Additional error members can be set by passing in an options list.
These include: id, status, code, meta, and links.
For more information on the JSON API standard for handling error objects check_origin:
[jsonapi.org](http://jsonapi.org/examples/#error-objects)
"""
def format(errors), do: format(errors, [])
def format(errors, conn) when is_map(conn), do: format(errors, [])
def format(%{__struct__: Ecto.Changeset} = cs, o), do: format(cs.errors, o)
def format(errors, opts) do
errors
|> Enum.map(&format_each(&1, opts[:opts]))
|> JaSerializer.ErrorSerializer.format()
end
def format(%{__struct__: Ecto.Changeset} = cs, _c, o),
do: format(cs.errors, o)
defp format_each({field, {message, vals}}, opts) do
# See https://github.com/elixir-ecto/ecto/blob/34a1012dd1f6d218c0183deb512b6c084afe3b6f/lib/ecto/changeset.ex#L1836-L1838
title =
Enum.reduce(vals, message, fn {key, value}, acc ->
case key do
:type -> acc
:fields -> acc
_ -> String.replace(acc, "%{#{key}}", to_string(value))
end
end)
%{
source: %{pointer: pointer_for(field)},
title: title,
detail: "#{Utils.humanize(field)} #{title}"
}
|> merge_opts(opts)
end
defp format_each({field, message}, opts) do
%{
source: %{pointer: pointer_for(field)},
title: message,
detail: "#{Utils.humanize(field)} #{message}"
}
|> merge_opts(opts)
end
defp merge_opts(error, nil), do: error
defp merge_opts(error, opts) when is_list(opts) do
opts = Enum.into(opts, %{})
Map.merge(error, opts)
end
defp merge_opts(error, _opts), do: error
# Assumes relationship name is the same as the field name without the id.
# This is a fairly large and incorrect assumption, but until we have better
# ideas this will work for most relationships.
defp pointer_for(field) do
case Regex.run(~r/(.*)_id$/, to_string(field)) do
nil -> "/data/attributes/#{Utils.format_key(field)}"
[_, rel] -> "/data/relationships/#{Utils.format_key(rel)}"
end
end
end