diff --git a/lib/validation.ex b/lib/validation.ex new file mode 100644 index 0000000..8213457 --- /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(do: spec) do + spec = Macro.escape(spec) + quote do + Validation.DSL.parse_schema(unquote(spec)) + end + end +end diff --git a/lib/validation/dsl.ex b/lib/validation/dsl.ex new file mode 100644 index 0000000..455f270 --- /dev/null +++ b/lib/validation/dsl.ex @@ -0,0 +1,41 @@ +defmodule Validation.DSL do + @moduledoc """ + 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 + """ + @spec parse_schema(schema_ast) :: Schema.t + def parse_schema(spec) do + rules = parse_rules(spec) + Schema.build(rules, %{}) + end + + defp parse_rules({:__block__, _, rules}) when is_list(rules) do + rules + |> Enum.flat_map(&parse_rules/1) + end + defp parse_rules({:required, _, [field]}) do + key_rule = Rules.RequiredKey.build(field) + [key_rule] + end + defp parse_rules({:required, _, [field, predicate_spec]}) do + predicate = parse_predicate(predicate_spec) + + value_rule = Rules.Value.build(field, predicate) + key_rule = Rules.RequiredKey.build(field) + + [key_rule, value_rule] + end + + defp parse_predicate({:filled?, _, nil}) do + Predicates.Filled.build + end +end diff --git a/test/dsl_test.exs b/test/dsl_test.exs new file mode 100644 index 0000000..9416837 --- /dev/null +++ b/test/dsl_test.exs @@ -0,0 +1,72 @@ +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 + + 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 + + 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" + + @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