From a069664db46668efb1c56a5e879fb7725e71d177 Mon Sep 17 00:00:00 2001 From: Lasse Skindstad Ebert Date: Fri, 10 Feb 2017 11:01:18 +0100 Subject: [PATCH 1/5] First stab at a public API, 16 I would like to change the calling stack after Validation.schema to not contain any macros. Just functions that accepts AST and returns data models. --- lib/validation.ex | 15 ++++++++++++++ lib/validation/dsl.ex | 33 ++++++++++++++++++++++++++++++ test/dsl_test.exs | 47 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 lib/validation.ex create mode 100644 lib/validation/dsl.ex create mode 100644 test/dsl_test.exs diff --git a/lib/validation.ex b/lib/validation.ex new file mode 100644 index 0000000..9ef9c90 --- /dev/null +++ b/lib/validation.ex @@ -0,0 +1,15 @@ +defmodule Validation do + @moduledoc """ + Contains top level convenience functions and macros that map into deeper modules + """ + + @doc """ + Creates a schema using the DSL + """ + defmacro schema(schema_spec) do + quote do + require Validation.DSL + Validation.DSL.build_schema(unquote(schema_spec)) + end + end +end diff --git a/lib/validation/dsl.ex b/lib/validation/dsl.ex new file mode 100644 index 0000000..dfecfb1 --- /dev/null +++ b/lib/validation/dsl.ex @@ -0,0 +1,33 @@ +defmodule Validation.DSL do + @moduledoc """ + Functions that make it simple to create schemas + """ + + @doc """ + Creates a schema from the AST specification + """ + defmacro build_schema(do: rule_spec) do + quote do + rules = Validation.DSL.parse_rules(unquote(rule_spec)) + Validation.Schema.build(rules, %{}) + end + end + + defmacro parse_rules({:required, _, [field, predicate_spec]}) do + quote do + predicate = Validation.DSL.parse_predicate(unquote(predicate_spec)) + field = unquote(field) + + value_rule = Validation.Rules.Value.build(field, predicate) + key_rule = Validation.Rules.RequiredKey.build(field) + + [key_rule, value_rule] + end + end + + defmacro parse_predicate({:filled?, _, nil}) do + quote do + Validation.Predicates.Filled.build + end + end +end diff --git a/test/dsl_test.exs b/test/dsl_test.exs new file mode 100644 index 0000000..c7e8ede --- /dev/null +++ b/test/dsl_test.exs @@ -0,0 +1,47 @@ +defmodule Validation.DSLTest do + use ExUnit.Case, async: true + + require Validation + alias Validation.Schema + + test "very simple schema" do + schema = Validation.schema do + required(:name, filled?) + end + + params = %{name: "John"} + result = Schema.apply(schema, params) + assert result.valid? + + params = %{first_name: "John"} + result = Schema.apply(schema, params) + refute result.valid? + end + + @tag :skip + test "multiple fields" + + @tag :skip + test "no predicate" + + @tag :skip + test "optional field" + + @tag :skip + test "nested schema" + + @tag :skip + test "custom predicate" + + @tag :skip + test "custom predicate combined with a built-in one" + + @tag :skip + test "custom rule" + + @tag :skip + test "white list" + + @tag :skip + test "strict" +end From f1f97d8929aadc9d4021ba0479b51619df1d4e6c Mon Sep 17 00:00:00 2001 From: Lasse Skindstad Ebert Date: Wed, 10 May 2017 11:26:16 +0200 Subject: [PATCH 2/5] Use fewer macros, 16 --- lib/validation/dsl.ex | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/validation/dsl.ex b/lib/validation/dsl.ex index dfecfb1..8aab938 100644 --- a/lib/validation/dsl.ex +++ b/lib/validation/dsl.ex @@ -7,27 +7,23 @@ defmodule Validation.DSL do Creates a schema from the AST specification """ defmacro build_schema(do: rule_spec) do + rule_spec = Macro.escape(rule_spec) quote do rules = Validation.DSL.parse_rules(unquote(rule_spec)) Validation.Schema.build(rules, %{}) end end - defmacro parse_rules({:required, _, [field, predicate_spec]}) do - quote do - predicate = Validation.DSL.parse_predicate(unquote(predicate_spec)) - field = unquote(field) + def parse_rules({:required, _, [field, predicate_spec]}) do + predicate = parse_predicate(predicate_spec) - value_rule = Validation.Rules.Value.build(field, predicate) - key_rule = Validation.Rules.RequiredKey.build(field) + value_rule = Validation.Rules.Value.build(field, predicate) + key_rule = Validation.Rules.RequiredKey.build(field) - [key_rule, value_rule] - end + [key_rule, value_rule] end - defmacro parse_predicate({:filled?, _, nil}) do - quote do - Validation.Predicates.Filled.build - end + defp parse_predicate({:filled?, _, nil}) do + Validation.Predicates.Filled.build end end From a72b4df7c1859afd1332492af75d32c3d5637ba4 Mon Sep 17 00:00:00 2001 From: Lasse Skindstad Ebert Date: Wed, 10 May 2017 11:31:32 +0200 Subject: [PATCH 3/5] Can parse multiple rules, 16 --- lib/validation/dsl.ex | 4 ++++ test/dsl_test.exs | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/validation/dsl.ex b/lib/validation/dsl.ex index 8aab938..5bbfb4f 100644 --- a/lib/validation/dsl.ex +++ b/lib/validation/dsl.ex @@ -14,6 +14,10 @@ defmodule Validation.DSL do end end + def parse_rules({:__block__, _, rules}) when is_list(rules) do + rules + |> Enum.flat_map(&parse_rules/1) + end def parse_rules({:required, _, [field, predicate_spec]}) do predicate = parse_predicate(predicate_spec) diff --git a/test/dsl_test.exs b/test/dsl_test.exs index c7e8ede..b784dc0 100644 --- a/test/dsl_test.exs +++ b/test/dsl_test.exs @@ -18,8 +18,21 @@ defmodule Validation.DSLTest do refute result.valid? end - @tag :skip - test "multiple fields" + test "multiple fields" do + schema = Validation.schema do + required(:name, filled?) + required(:email, filled?) + end + + params = %{name: "John", email: "john@me.com"} + result = Schema.apply(schema, params) + assert result.valid? + + params = %{name: "John"} + result = Schema.apply(schema, params) + refute result.valid? + assert result.errors == %{email: ["is missing", "must be filled"]} + end @tag :skip test "no predicate" From 7b4624c9fb94ceb0b93154823a0d18728e1325b2 Mon Sep 17 00:00:00 2001 From: Lasse Skindstad Ebert Date: Wed, 10 May 2017 11:33:49 +0200 Subject: [PATCH 4/5] Build required rule without predicate, 16 --- lib/validation/dsl.ex | 4 ++++ test/dsl_test.exs | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/validation/dsl.ex b/lib/validation/dsl.ex index 5bbfb4f..b32dd73 100644 --- a/lib/validation/dsl.ex +++ b/lib/validation/dsl.ex @@ -18,6 +18,10 @@ defmodule Validation.DSL do rules |> Enum.flat_map(&parse_rules/1) end + def parse_rules({:required, _, [field]}) do + key_rule = Validation.Rules.RequiredKey.build(field) + [key_rule] + end def parse_rules({:required, _, [field, predicate_spec]}) do predicate = parse_predicate(predicate_spec) diff --git a/test/dsl_test.exs b/test/dsl_test.exs index b784dc0..9416837 100644 --- a/test/dsl_test.exs +++ b/test/dsl_test.exs @@ -34,8 +34,20 @@ defmodule Validation.DSLTest do assert result.errors == %{email: ["is missing", "must be filled"]} end - @tag :skip - test "no predicate" + test "no predicate" do + schema = Validation.schema do + required(:name) + end + + params = %{name: ""} + result = Schema.apply(schema, params) + assert result.valid? + + params = %{first_name: "John"} + result = Schema.apply(schema, params) + refute result.valid? + assert result.errors == %{name: ["is missing"]} + end @tag :skip test "optional field" From 6d3f25a25063d74e912dc23679f46bb44ad48219 Mon Sep 17 00:00:00 2001 From: Lasse Skindstad Ebert Date: Wed, 10 May 2017 11:47:47 +0200 Subject: [PATCH 5/5] Better interface for DSL module, 16 --- lib/validation.ex | 6 +++--- lib/validation/dsl.ex | 30 +++++++++++++++++------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/validation.ex b/lib/validation.ex index 9ef9c90..8213457 100644 --- a/lib/validation.ex +++ b/lib/validation.ex @@ -6,10 +6,10 @@ defmodule Validation do @doc """ Creates a schema using the DSL """ - defmacro schema(schema_spec) do + defmacro schema(do: spec) do + spec = Macro.escape(spec) quote do - require Validation.DSL - Validation.DSL.build_schema(unquote(schema_spec)) + Validation.DSL.parse_schema(unquote(spec)) end end end diff --git a/lib/validation/dsl.ex b/lib/validation/dsl.ex index b32dd73..455f270 100644 --- a/lib/validation/dsl.ex +++ b/lib/validation/dsl.ex @@ -3,35 +3,39 @@ defmodule Validation.DSL do Functions that make it simple to create schemas """ + alias Validation.Predicates + alias Validation.Rules + alias Validation.Schema + + @type schema_ast :: any + @doc """ Creates a schema from the AST specification """ - defmacro build_schema(do: rule_spec) do - rule_spec = Macro.escape(rule_spec) - quote do - rules = Validation.DSL.parse_rules(unquote(rule_spec)) - Validation.Schema.build(rules, %{}) - end + @spec parse_schema(schema_ast) :: Schema.t + def parse_schema(spec) do + rules = parse_rules(spec) + Schema.build(rules, %{}) end - def parse_rules({:__block__, _, rules}) when is_list(rules) do + defp parse_rules({:__block__, _, rules}) when is_list(rules) do rules |> Enum.flat_map(&parse_rules/1) end - def parse_rules({:required, _, [field]}) do - key_rule = Validation.Rules.RequiredKey.build(field) + defp parse_rules({:required, _, [field]}) do + key_rule = Rules.RequiredKey.build(field) [key_rule] end - def parse_rules({:required, _, [field, predicate_spec]}) do + defp parse_rules({:required, _, [field, predicate_spec]}) do predicate = parse_predicate(predicate_spec) - value_rule = Validation.Rules.Value.build(field, predicate) - key_rule = Validation.Rules.RequiredKey.build(field) + value_rule = Rules.Value.build(field, predicate) + key_rule = Rules.RequiredKey.build(field) [key_rule, value_rule] end defp parse_predicate({:filled?, _, nil}) do - Validation.Predicates.Filled.build + Predicates.Filled.build end end