Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimise valid?/2 #66

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions lib/norm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,7 @@ defmodule Norm do
false
"""
def valid?(input, spec) do
case Conformer.conform(spec, input) do
{:ok, _} -> true
{:error, _} -> false
end
Conformer.valid?(spec, input)
end

@doc ~S"""
Expand Down
18 changes: 16 additions & 2 deletions lib/norm/conformer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ defmodule Norm.Conformer do
Norm.Conformer.Conformable.conform(spec, input, [])
end

def valid?(spec, input) do
Norm.Conformer.Conformable.valid?(spec, input, [])
end

def group_results(results) do
results
|> Enum.reduce(%{ok: [], error: []}, fn {result, s}, acc ->
Expand Down Expand Up @@ -49,10 +53,12 @@ defmodule Norm.Conformer do
defprotocol Conformable do
@moduledoc false
# Defines a conformable type. Must take the type, current path, and input and
# return an success tuple with the conformed data or a list of errors.
# return a success tuple with the conformed data or a list of errors.

# @fallback_to_any true
def conform(spec, path, input)

def valid?(spec, path, input)
end
end

Expand All @@ -71,12 +77,16 @@ defimpl Norm.Conformer.Conformable, for: Atom do
{:error, [Conformer.error(path, input, "is not an atom.")]}

atom != input ->
{:error, [Conformer.error(path, input, "== :#{atom}")]}
{:error, [Conformer.error(path, input, "== #{inspect(atom)}")]}

true ->
{:ok, atom}
end
end

def valid?(atom, input, path) do
conform(atom, input, path) |> elem(0) == :ok
end
end

defimpl Norm.Conformer.Conformable, for: Tuple do
Expand Down Expand Up @@ -105,4 +115,8 @@ defimpl Norm.Conformer.Conformable, for: Tuple do
{:ok, List.to_tuple(results.ok)}
end
end

def valid?(spec, input, path) do
conform(spec, input, path) |> elem(0) == :ok
end
end
7 changes: 6 additions & 1 deletion lib/norm/core/all_of.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ defmodule Norm.Core.AllOf do
{:ok, Enum.at(result.ok, 0)}
end
end

def valid?(%{specs: specs}, input, path) do
specs
|> Stream.map(fn spec -> Conformable.valid?(spec, input, path) end)
|> Enum.all?(& &1)
end
end
end

6 changes: 6 additions & 0 deletions lib/norm/core/alt.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ defmodule Norm.Core.Alt do
{:error, List.flatten(result.error)}
end
end

def valid?(%{specs: specs}, input, path) do
specs
|> Stream.map(fn {name, spec} -> Conformable.valid?(spec, input, path ++ [name]) end)
|> Enum.any?(& &1)
end
end

if Code.ensure_loaded?(StreamData) do
Expand Down
6 changes: 6 additions & 0 deletions lib/norm/core/any_of.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ defmodule Norm.Core.AnyOf do
{:error, List.flatten(result.error)}
end
end

def valid?(%{specs: specs}, input, path) do
specs
|> Stream.map(fn spec -> Conformable.valid?(spec, input, path) end)
|> Enum.any?(& &1)
end
end

if Code.ensure_loaded?(StreamData) do
Expand Down
15 changes: 15 additions & 0 deletions lib/norm/core/collection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ defmodule Norm.Core.Collection do
end
end

def valid?(%{spec: spec, opts: opts}, input, path) do
with :ok <- check_enumerable(input, path, opts),
:ok <- check_kind_of(input, path, opts),
:ok <- check_distinct(input, path, opts),
:ok <- check_counts(input, path, opts) do
input
|> Stream.with_index()
|> Stream.map(fn {elem, i} -> Conformable.valid?(spec, elem, path ++ [i]) end)
|> Enum.all?(& &1)

else
_ -> false
end
end

defp convert(results, type) do
Enum.into(results, type)
end
Expand Down
21 changes: 21 additions & 0 deletions lib/norm/core/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ defmodule Norm.Core.Schema do
end
end

def valid?(%Schema{specs: specs}, %{__struct__: module} = input, path) when not is_nil(module) do
check_specs_validity(specs, Map.from_struct(input), path)
end

def valid?(%Schema{specs: specs}, input, path) do
check_specs_validity(specs, input, path)
end

defp check_specs_validity(specs, input, path) do
input
|> Stream.map(fn spec -> check_spec_validity(spec, specs, path) end)
|> Enum.all?(& &1)
end

defp check_spec_validity({key, value}, specs, path) do
case Map.get(specs, key) do
nil -> true
spec -> Conformable.valid?(spec, value, path ++ [key])
end
end

defp check_specs(specs, input, path) do
results =
input
Expand Down
18 changes: 18 additions & 0 deletions lib/norm/core/selection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,24 @@ defmodule Norm.Core.Selection do
end
end

def valid?(%{required: required, schema: schema}, input, path) do
Conformable.valid?(schema, input, path) && valid_keys?(required, input)
end

defp valid_keys?([] = _required, _input), do: true

defp valid_keys?([{key, _inner} | rest], input) do
if valid_key?(key, input), do: valid_keys?(rest, input), else: false
end

defp valid_keys?([key | rest], input) do
if valid_key?(key, input), do: valid_keys?(rest, input), else: false
end

defp valid_key?(key, input) when is_map(input), do: Map.has_key?(input, key)

defp valid_key?(_key, _input), do: true

defp ensure_keys([], _conformed, _path, errors), do: errors
defp ensure_keys([{key, inner} | rest], conformed, path, errors) do
case ensure_key(key, conformed, path) do
Expand Down
5 changes: 5 additions & 0 deletions lib/norm/core/spec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ defmodule Norm.Core.Spec do
raise ArgumentError, "Predicates must return a boolean value"
end
end

def valid?(%{f: _f, predicate: _pred} = spec, input, path) do
{status, _} = conform(spec, input, path)
status == :ok
end
end

@doc false
Expand Down
4 changes: 4 additions & 0 deletions lib/norm/core/spec/and.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ defmodule Norm.Core.Spec.And do
Conformable.conform(r, input, path)
end
end

def valid?(%{left: l, right: r}, input, path) do
Conformable.valid?(l, input, path) && Conformable.valid?(r, input, path)
end
end

if Code.ensure_loaded?(StreamData) do
Expand Down
4 changes: 4 additions & 0 deletions lib/norm/core/spec/or.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ defmodule Norm.Core.Spec.Or do
end
end
end

def valid?(%{left: l, right: r}, input, path) do
Conform.valid?(l, input, path) or Conform.valid?(r, input, path)
end
end

if Code.ensure_loaded?(StreamData) do
Expand Down
4 changes: 4 additions & 0 deletions lib/norm/generator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ defmodule Norm.Generator do
def conform(%{conformer: c}, input, path) do
Norm.Conformer.Conformable.conform(c, input, path)
end

def valid?(%{conformer: c}, input, path) do
Norm.Conformer.Conformable.valid?(c, input, path)
end
end

defimpl Norm.Generatable do
Expand Down