Current section
Files
Jump to
Current section
Files
lib/validex.ex
defmodule Validex do
@moduledoc """
Documentation for Validex.
"""
@doc """
Verify data against schema returning both valid and invalid validations.
## Examples
iex> Validex.verify(%{ name: 5 }, [name: :string])
[{:ok, :name, :presence}, {:error, :name, :type, "name should be string but was integer"}]
"""
def verify(data, schema) when is_map(data) and is_list(schema) do
expanded_schema = Enum.map(schema, fn {attribute, spec} ->
{attribute, expand_rules(expanders(), attribute, spec)}
end)
result = Enum.flat_map(expanded_schema, fn {attribute, rules} when is_list(rules) ->
value = Map.get(data, attribute, :__validex_missing__)
Enum.flat_map(rules, fn {rule_kind, rule_spec} ->
validator = find_validator(validators(), rule_kind)
validator.validate(rule_kind, attribute, rule_spec, value)
end)
{attribute, shorthand} when is_atom(shorthand) ->
value = Map.get(data, attribute, :__validex_missing__)
Validex.Validators.Unknown.validate(shorthand, attribute, shorthand, value)
end)
result |> Enum.uniq() |> Enum.sort(
fn a, b ->
ak = elem(a, 1)
bk = elem(b, 1)
case {ak, bk} do
{ak, bk} when is_atom(ak) and is_list(bk) -> {[ak], elem(a, 2)} <= {bk, elem(b, 2)}
{ak, bk} when is_list(ak) and is_atom(bk) -> {ak, elem(a, 2)} <= {[bk], elem(b, 2)}
{ak, bk} -> {ak, elem(a, 2)} <= {bk, elem(b, 2)}
end
end)
end
@doc """
Verify data against schema returning only invalid validations
## Examples
iex> Validex.errors(%{ name: 5 }, [name: :string])
[{:error, :name, :type, "name should be string but was integer"}]
"""
def errors(data, schema) do
verify(data, schema) |> Enum.filter(&match?({:error, _, _, _}, &1))
end
@doc """
Verify data against schema returning true if no erros were found
## Examples
iex> Validex.valid?(%{ name: "simon" }, [name: :string])
true
iex> Validex.valid?(%{ name: 455 }, [name: :string])
false
"""
def valid?(data, schema) do
errors(data, schema) == []
end
defp find_validator(validators, rule_kind) do
case Enum.find(validators, &(&1.rule_kind() == rule_kind)) do
nil -> Validex.Validators.Unknown
validator -> validator
end
end
defp expand_rules(expanders, attribute, spec) do
rules = expanders
|> Enum.map(&(&1.expand(attribute, spec)))
|> Enum.filter(&(&1 != spec))
|> Enum.concat |> Enum.uniq
if rules == [] do
spec
else
expand_rules(expanders, attribute, rules)
end
end
defp validators() do
load_plugins()
Agent.get(__MODULE__, &Map.fetch!(&1, :validators)) |> Enum.uniq
end
defp expanders() do
load_plugins()
Agent.get(__MODULE__, &Map.fetch!(&1, :expanders)) |> Enum.uniq
end
defp load_plugins() do
unless Process.whereis(__MODULE__) do
Agent.start(fn -> Map.new() end, name: __MODULE__)
Agent.update(__MODULE__, &Map.merge(&1, %{
expanders: Validex.PluginLoader.load_all(Validex.RuleExpander),
validators: Validex.PluginLoader.load_all(Validex.Validator)
}))
end
end
end