Skip to content

Commit

Permalink
Merge branch 'derekkraan-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
keathley committed Oct 25, 2020
2 parents 2646abe + 535ea09 commit 12cdf14
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 0 deletions.
15 changes: 15 additions & 0 deletions lib/norm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defmodule Norm do
Schema,
Selection,
Spec,
Delegate
}

@doc false
Expand Down Expand Up @@ -150,6 +151,20 @@ defmodule Norm do
Spec.build(predicate)
end

@doc ~S"""
Allows encapsulation of a spec in another function. This enables late-binding of
specs which enables definition of recursive specs.
## Examples:
iex> conform!(%{"value" => 1, "left" => %{"value" => 2, "right" => %{"value" => 4}}}, Norm.Core.DelegateTest.TreeTest.spec())
%{"value" => 1, "left" => %{"value" => 2, "right" => %{"value" => 4}}}
iex> conform(%{"value" => 1, "left" => %{"value" => 2, "right" => %{"value" => 4, "right" => %{"value" => "12"}}}}, Norm.Core.DelegateTest.TreeTest.spec())
{:error, [%{input: "12", path: ["left", "right", "right", "value"], spec: "is_integer()"}]}
"""
def delegate(predicate) do
Delegate.build(predicate)
end

@doc ~S"""
Creates a re-usable schema. Schema's are open which means that all keys are
optional and any non-specified keys are passed through without being conformed.
Expand Down
15 changes: 15 additions & 0 deletions lib/norm/core/delegate.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Norm.Core.Delegate do
@moduledoc false

defstruct [:fun]

def build(fun) when is_function(fun, 0) do
%__MODULE__{fun: fun}
end

defimpl Norm.Conformer.Conformable do
def conform(%{fun: fun}, input, path) do
Norm.Conformer.Conformable.conform(fun.(), input, path)
end
end
end
35 changes: 35 additions & 0 deletions test/norm/core/delegate_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule Norm.Core.DelegateTest do
use Norm.Case, async: true

defmodule TreeTest do
def spec() do
schema(%{
"value" => spec(is_integer()),
"left" => delegate(&TreeTest.spec/0),
"right" => delegate(&TreeTest.spec/0)
})
end
end

describe "delegate/1" do
test "can write recursive specs with 'delegate'" do
assert {:ok, _} = conform(%{}, TreeTest.spec())

assert {:ok, _} =
conform(
%{"value" => 4, "left" => %{"value" => 2}, "right" => %{"value" => 12}},
TreeTest.spec()
)

assert {:error, [%{input: "12", path: ["left", "left", "value"], spec: "is_integer()"}]} =
conform(
%{
"value" => 4,
"left" => %{"value" => 2, "left" => %{"value" => "12"}},
"right" => %{"value" => 12}
},
TreeTest.spec()
)
end
end
end

0 comments on commit 12cdf14

Please sign in to comment.