Current section

Files

Jump to
eview lib eview changeset validators metadata_validator.ex
Raw

lib/eview/changeset/validators/metadata_validator.ex

if Code.ensure_loaded?(Ecto) do
defmodule EView.Changeset.Validators.Metadata do
@moduledoc """
This helper validates metadata corresponding to
[API Manifest](http://docs.apimanifest.apiary.io/#introduction/optional-features/metadata).
"""
import Ecto.Changeset
@max_key_length 100
@max_value_length 500
@max_list_length 25
@max_list_value_length 100
@jsonpath_joiner "."
def validate_metadata(changeset, field, opts \\ []) do
validate_change changeset, field, :metadata, fn _, value ->
get_metadata_errors(field, value, opts)
end
end
defp get_metadata_errors(field, metadata, _opts) when is_map(metadata) do
Enum.reduce(metadata, [], &field_validation_reducer(field, &1, &2))
end
defp get_metadata_errors(field, _, opts) do
[{field, {message(opts, "is not a valid metadata object"), [validation: :metadata]}}]
end
# Convert atom keys to strings
defp field_validation_reducer(parent, {key, val}, acc) when is_atom(key) do
field_validation_reducer(parent, {to_string(key), val}, acc)
end
# Check max length of key
defp field_validation_reducer(parent, {key, _}, acc) when is_binary(key) and byte_size(key) > @max_key_length do
[{field_path(parent, key),
{"key should be up to %{max} characters", [validation: :length, max: @max_key_length]}}
| acc]
end
# Check binary value max length
defp field_validation_reducer(parent, {key, val}, acc) when is_binary(val) and byte_size(val) > @max_value_length do
[{field_path(parent, key),
{"value should be up to %{max} characters", [validation: :length, max: @max_value_length]}}
| acc]
end
# Check list values
defp field_validation_reducer(parent, {key, list}, acc) when is_list(list) and length(list) > @max_list_length do
[{field_path(parent, key),
{"lists should be up to %{max} elements", [validation: :length, max: @max_list_length]}}
| acc]
end
# Check list elements length
defp field_validation_reducer(parent, {key, list}, acc) when is_list(list) do
{errors, _} = Enum.reduce(list, {[], 0}, fn
%Decimal{}, {el_acc, i} ->
{el_acc, i + 1}
elem, {el_acc, i} when is_float(elem) or is_number(elem) ->
{el_acc, i + 1}
elem, {el_acc, i} when is_binary(elem) and byte_size(elem) <= @max_list_value_length ->
{el_acc, i + 1}
elem, {el_acc, i} when is_binary(elem) and byte_size(elem) > @max_list_value_length ->
{[{join_atoms(field_path(parent, key), "[#{i}]"),
{"list keys should be up to %{max} characters", [validation: :length, max: @max_key_length]}}
| el_acc], i + 1}
_, {el_acc, i} ->
{[{field_path(parent, key),
{"one of keys is invalid", [validation: :cast, type: [:integer, :float, :decimal, :string]]}}
| el_acc], i + 1}
end)
errors ++ acc
end
# Pass integers, decimals and floats
defp field_validation_reducer(_parent, {_, number}, acc) when is_float(number) or is_integer(number), do: acc
defp field_validation_reducer(_parent, {_, %Decimal{}}, acc), do: acc
# Pass valid binary keys and values
defp field_validation_reducer(_parent, {key, val}, acc)
when is_binary(key) and byte_size(key) <= @max_key_length
and is_binary(val) and byte_size(val) <= @max_value_length do
acc
end
# Everything else is an error
defp field_validation_reducer(parent, {key, _val}, acc) do
[{field_path(parent, key),
{"is invalid", [validation: :cast, type: [:integer, :float, :decimal, :string]]}}
| acc]
end
# Join atom in JSON Path style
defp field_path(parent, child) do
String.to_atom(to_string(parent) <> @jsonpath_joiner <> to_string(child))
end
# Join strings or atoms and return a string
defp join_atoms(prefix, sufix) do
String.to_atom(to_string(prefix) <> to_string(sufix))
end
# Extract message from options
defp message(opts, key \\ :message, default) do
Keyword.get(opts, key, default)
end
end
end