defmodule Drops.Types.Map.Key do
@moduledoc false
alias __MODULE__
alias Drops.Type.Validator
defstruct [:path, :presence, :type]
defimpl Drops.Type.Validator, for: Key do
def validate(type, data), do: Key.validate(type, data)
end
def validate(%Key{presence: presence, path: path} = key, data) do
if present?(data, key) do
result = Validator.validate(key.type, get_in(data, path))
nest_result(result, path)
else
case presence do
:required -> {:error, {path, [input: data, predicate: :has_key?, args: [path]]}}
:optional -> :ok
end
end
end
def stringify(key) do
%Key{path: Enum.map(key.path, &to_string/1), presence: key.presence, type: key.type}
end
def present?(map, _) when not is_map(map) do
true
end
def present?(_map, []) do
true
end
def present?(map, %Key{} = key) do
present?(map, key.path)
end
def present?(map, [key | tail]) do
Map.has_key?(map, key) and present?(map[key], tail)
end
defp nest_result({:error, {:or, {left, right, opts}}}, root) do
{:error,
{:or,
{nest_result(left, root), nest_result(right, root), Keyword.merge(opts, path: root)}}}
end
defp nest_result({:error, {:list, results}}, root) when is_list(results) do
{:error, {root, {:list, Enum.with_index(results, &nest_result(&1, root ++ [&2]))}}}
end
defp nest_result({:error, {:list, result}}, root) when is_tuple(result) do
{:error, {root, result}}
end
defp nest_result(results, root) when is_list(results) do
Enum.map(results, &nest_result(&1, root))
end
defp nest_result({outcome, {path, result}}, root) when is_list(path) do
{outcome, {root ++ path, result}}
end
defp nest_result({outcome, value}, root) do
{outcome, {root, value}}
end
end