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 selection.ex
Raw

lib/norm/spec/selection.ex

defmodule Norm.Spec.Selection do
@moduledoc false
# Provides the definition for selections
defstruct [subset: nil]
alias Norm.Schema
alias Norm.SpecError
def new(schema, path) do
select(schema, path, %{})
end
defp select(_, [], selection), do: %__MODULE__{subset: selection}
defp select(schema, [selector | rest], selection) do
case selector do
{key, inner} ->
case Schema.spec(schema, key) do
nil ->
raise SpecError, {:selection, key, schema}
inner_schema ->
selection = Map.put(selection, key, select(inner_schema, inner, %{}))
select(schema, rest, selection)
end
key ->
case Schema.spec(schema, key) do
nil ->
raise SpecError, {:selection, key, schema}
spec ->
new_selection = Map.put(selection, key, spec)
select(schema, rest, new_selection)
end
end
end
defimpl Norm.Generatable do
alias Norm.Generatable
def gen(%{subset: specs}) do
case Enum.reduce(specs, %{}, &to_gen/2) do
{:error, error} ->
{:error, error}
gen ->
{:ok, StreamData.fixed_map(gen)}
end
end
defp to_gen(_, {:error, error}), do: {:error, error}
defp to_gen({key, spec}, generator) do
case Generatable.gen(spec) do
{:ok, g} ->
Map.put(generator, key, g)
{:error, error} ->
{:error, error}
end
end
end
defimpl Norm.Conformer.Conformable do
alias Norm.Conformer.Conformable
def conform(%{subset: subset}, input, path) do
results =
subset
|> Enum.map(fn {key, spec} ->
val = Map.get(input, key)
if val do
{key, Conformable.conform(spec, val, path ++ [key])}
else
{key, {:error, [error(path ++ [key], input, ":required")]}}
end
end)
|> Enum.reduce(%{ok: [], error: []}, fn {key, {result, r}}, acc ->
Map.put(acc, result, [{key, r} | acc[result]])
end)
if Enum.any?(results.error) do
errors =
results.error
|> Enum.flat_map(fn {_, errors} -> errors end)
{:error, errors}
else
{:ok, Enum.into(results.ok, %{})}
end
end
defp error(path, input, msg) do
%{path: path, input: input, msg: msg, at: nil}
end
end
end