Skip to content

Commit ffa0272

Browse files
authored
Merge branch 'master' into add_recommended_combinations_of_elixir_and_erlang_versions_to_ci
2 parents 4d80f09 + 12cdf14 commit ffa0272

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

lib/norm.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ defmodule Norm do
1616
Schema,
1717
Selection,
1818
Spec,
19+
Delegate
1920
}
2021

2122
@doc false
@@ -150,6 +151,20 @@ defmodule Norm do
150151
Spec.build(predicate)
151152
end
152153

154+
@doc ~S"""
155+
Allows encapsulation of a spec in another function. This enables late-binding of
156+
specs which enables definition of recursive specs.
157+
158+
## Examples:
159+
iex> conform!(%{"value" => 1, "left" => %{"value" => 2, "right" => %{"value" => 4}}}, Norm.Core.DelegateTest.TreeTest.spec())
160+
%{"value" => 1, "left" => %{"value" => 2, "right" => %{"value" => 4}}}
161+
iex> conform(%{"value" => 1, "left" => %{"value" => 2, "right" => %{"value" => 4, "right" => %{"value" => "12"}}}}, Norm.Core.DelegateTest.TreeTest.spec())
162+
{:error, [%{input: "12", path: ["left", "right", "right", "value"], spec: "is_integer()"}]}
163+
"""
164+
def delegate(predicate) do
165+
Delegate.build(predicate)
166+
end
167+
153168
@doc ~S"""
154169
Creates a re-usable schema. Schema's are open which means that all keys are
155170
optional and any non-specified keys are passed through without being conformed.

lib/norm/core/delegate.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule Norm.Core.Delegate do
2+
@moduledoc false
3+
4+
defstruct [:fun]
5+
6+
def build(fun) when is_function(fun, 0) do
7+
%__MODULE__{fun: fun}
8+
end
9+
10+
defimpl Norm.Conformer.Conformable do
11+
def conform(%{fun: fun}, input, path) do
12+
Norm.Conformer.Conformable.conform(fun.(), input, path)
13+
end
14+
end
15+
end

test/norm/core/delegate_test.exs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
defmodule Norm.Core.DelegateTest do
2+
use Norm.Case, async: true
3+
4+
defmodule TreeTest do
5+
def spec() do
6+
schema(%{
7+
"value" => spec(is_integer()),
8+
"left" => delegate(&TreeTest.spec/0),
9+
"right" => delegate(&TreeTest.spec/0)
10+
})
11+
end
12+
end
13+
14+
describe "delegate/1" do
15+
test "can write recursive specs with 'delegate'" do
16+
assert {:ok, _} = conform(%{}, TreeTest.spec())
17+
18+
assert {:ok, _} =
19+
conform(
20+
%{"value" => 4, "left" => %{"value" => 2}, "right" => %{"value" => 12}},
21+
TreeTest.spec()
22+
)
23+
24+
assert {:error, [%{input: "12", path: ["left", "left", "value"], spec: "is_integer()"}]} =
25+
conform(
26+
%{
27+
"value" => 4,
28+
"left" => %{"value" => 2, "left" => %{"value" => "12"}},
29+
"right" => %{"value" => 12}
30+
},
31+
TreeTest.spec()
32+
)
33+
end
34+
end
35+
end

0 commit comments

Comments
 (0)