-
Notifications
You must be signed in to change notification settings - Fork 148
/
ecto_error_serializer.ex
91 lines (73 loc) · 2.58 KB
/
ecto_error_serializer.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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