If you provide a string to an :integer field you get errors: [count: {"is invalid", [type: :integer, validation: :cast]}]
in a changeset, which is not helpful for a user. Unfortunately Ecto.Schema.field/3 doesn’t accept custom error messages (as of v3.9 at least).
I want to return something like “should be a number” to a user instead of “is invalid”.
One solution could be a create a custom integer type, which can return a custom error message. Another way could be processing this in web layer in some kind of humanize_error function, replacing default “is invalid” with “should be a number”.
For now I’ve settled with custom ecto types, but as their number started to grow, I want to re-evaluate my approach. So I wonder how do others solve similar issues? I would appreciate your thoughts.
PS. What I like about custom types, is that I can also use them to clean up values before casting them. I can trim strings, remove double spaces, drop “.0” and “,0” from integers.
The metadata in the error would allow you to do the transformation you mention to the error in Ecto.Changeset.traverse_errors (which is likely already called somewhere in your codebase). You can ignore the existing error string and match on metadata[:validation] + metadata[:type].
I made a post discussing how we solved this issue.
It boils down to creating a function
@doc """
Overwrites the error message for `Ecto.Changeset.cast/4` from `"is invalid"` to the
custom supplied error message.
"""
def custom_cast_error_message(changeset, field, message) do
errors = Enum.map(changeset.errors, fn {error_field, {_msg, opts}} = error ->
if field == error_field && opts[:validation] == :cast do
{field, {message, opts}}
else
error
end
end)
%{changeset | errors: errors}
end
and calling it like
message = "This is our custom error message for cast/4"
Ecto.Changeset.cast(answer, params, [:value])
|> Aw.Changeset.custom_cast_error_message(:value, message)