Packages

Norm is a system for specifying the structure of data. It can be used for validation and for generation of data. Norm does not provide any set of predicates and instead allows you to re-use any of your existing validations.

Current section

Files

Jump to
norm lib norm spec collection.ex
Raw

lib/norm/spec/collection.ex

defmodule Norm.Spec.Collection do
@moduledoc false
defstruct spec: nil, opts: []
def new(spec, opts) do
%__MODULE__{spec: spec, opts: opts}
end
defimpl Norm.Conformer.Conformable do
alias Norm.Conformer
alias Norm.Conformer.Conformable
def conform(%{spec: spec, opts: opts}, input, path) do
with :ok <- check_enumerable(input, path, opts),
:ok <- check_map_of(input, path, opts),
:ok <- check_distinct(input, path, opts),
:ok <- check_counts(input, path, opts) do
results =
input
|> Enum.with_index()
|> Enum.map(fn {elem, i} -> Conformable.conform(spec, elem, path ++ [i]) end)
|> Conformer.group_results()
if Enum.any?(results.error) do
{:error, results.error}
else
{:ok, convert(results.ok, opts[:into])}
end
end
end
defp convert(results, type) do
Enum.into(results, type)
end
defp check_counts(input, path, opts) do
min = opts[:min_count]
max = opts[:max_count]
length = Enum.count(input)
cond do
min > length ->
{:error, [Conformer.error(path, input, "min_count: #{min}")]}
max < length ->
{:error, [Conformer.error(path, input, "max_count: #{max}")]}
true ->
:ok
end
end
defp check_distinct(input, path, opts) do
if opts[:distinct] do
if Enum.uniq(input) == input do
:ok
else
{:error, [Conformer.error(path, input, "distinct?")]}
end
else
:ok
end
end
defp check_enumerable(input, path, _opts) do
if Enumerable.impl_for(input) == nil do
{:error, [Conformer.error(path, input, "not enumerable")]}
else
:ok
end
end
defp check_map_of(input, path, opts) do
cond do
# if coll_of was used, accept every kind of list
opts[:into] == [] ->
:ok
# in case of map_of, check the format of the enumerable
Enum.all?(input, &match?({_, _}, &1)) ->
:ok
true ->
{:error, [Conformer.error(path, input, "not a map")]}
end
end
end
if Code.ensure_loaded?(StreamData) do
defimpl Norm.Generatable do
def gen(%{spec: spec, opts: opts}) do
with {:ok, g} <- Norm.Generatable.gen(spec) do
generator =
g
|> sequence(opts)
|> into(opts)
{:ok, generator}
end
end
def sequence(g, opts) do
min = opts[:min_count]
max = opts[:max_count]
if opts[:distinct] do
StreamData.uniq_list_of(g, [min_length: min, max_length: max])
else
StreamData.list_of(g, [min_length: min, max_length: max])
end
end
def into(list_gen, opts) do
StreamData.bind(list_gen, fn list ->
StreamData.constant(Enum.into(list, opts[:into]))
end)
end
end
end
end