Skip to content

Commit

Permalink
Merge branch 'master' into optimise-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
gesta authored Mar 23, 2021
2 parents a81181b + f6cc0d6 commit b8268be
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 8 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ user_schema = schema(%{
input = %{user: %{name: "chris", age: 30, email: "[email protected]"}}

conform!(input, user_schema)
=> %{user: %{name: "chris", age: 30}}
=> %{user: %{name: "chris", age: 30, email: "[email protected]"}}

user_schema
|> gen()
|> Enum.take(3)
=> [
%{user: %{age: 0, name: ""}},
%{user: %{age: 2, name: "x"}},
%{user: %{age: -2, name: ""}}
%{user: %{age: 1, name: ""}}
]
```

Expand Down
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 @@ -147,6 +148,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
13 changes: 7 additions & 6 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
"credo": {:hex, :credo, "1.5.1", "4fe303cc828412b9d21eed4eab60914c401e71f117f40243266aafb66f30d036", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0b219ca4dcc89e4e7bc6ae7e6539c313e738e192e10b85275fa1e82b5203ecd7"},
"earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"},
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
"ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
"file_system": {:hex, :file_system, "0.2.9", "545b9c9d502e8bfa71a5315fac2a923bd060fd9acb797fe6595f54b0f975fd32", [:mix], [], "hexpm", "3cf87a377fe1d93043adeec4889feacf594957226b4f19d5897096d6f61345d8"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
}
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 b8268be

Please sign in to comment.