defmodule Draft do
@moduledoc """
Data Validation for Elixir.
"""
alias Draft.Extract
def valid?(data) when is_struct(data) do
valid?(data, Extract.rules(data))
end
def valid?(data, settings) do
errors(data, settings) == []
end
def validate(data) when is_struct(data) do
validate(data, Extract.rules(data))
end
def validate(data, settings) do
case errors(data, settings) do
errors when errors != [] -> {:error, errors}
_ -> {:ok, data}
end
end
def errors(data) when is_struct(data) do
errors(data, Extract.rules(data))
end
def errors(data, settings) do
results(data, settings)
end
def results(data) when is_struct(data) do
results(data, Extract.rules(data))
end
def results(data, settings)
when not(is_list(data)) and not(is_map(data)) do
results([value: data], value: settings)
end
def results(data, settings) do
settings
|> Enum.map(fn {key, rules} ->
Enum.reduce_while(rules, [], fn rule, _acc ->
{name, opts} =
cond do
is_tuple(rule) ->
rule
is_atom(rule) ->
{rule, []}
true ->
message = "rule must be atom or {name, opts} not #{rule}"
raise ArgumentError, message: message
end
validator = Draft.Registry.validator(name)
exists = Map.has_key?(data, key)
cond do
exists and is_atom(validator) and not(is_nil(validator)) ->
value = Map.get(data, key)
case validator.validate(value, data, opts) do
{:ok, _} ->
{:cont, []}
{:error, error} ->
{:halt, [{key, error}]}
end
true ->
{:cont, []}
end
end)
end)
|> List.flatten()
end
def validate_required(attr, required)
def validate_required(attr, required) when is_list(attr) do
attr
|> Enum.into(%{})
|> validate_required(required)
end
def validate_required(attr, required) when is_map(required) do
validate_required(attr, Map.to_list(required))
end
def validate_required(attr, required) when is_map(attr) and is_list(required) do
notfound =
Enum.find(required, fn key ->
attr
|> Map.has_key?(key)
|> Kernel.not()
end)
if is_nil(notfound) do
{:ok, attr}
else
{:error, notfound}
end
end
end