Skip to content

Commit

Permalink
Support full Sum-errors with extra predicates
Browse files Browse the repository at this point in the history
  • Loading branch information
solnic committed Oct 12, 2023
1 parent e8bdaed commit 9444154
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 15 deletions.
7 changes: 7 additions & 0 deletions lib/drops/types/map/dsl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ defmodule Drops.Types.Map.DSL do
@spec type(atom(), []) :: type()
@spec type({:cast, {atom(), []}}, type()) :: type()

def type([type | rest], predicates) when is_list(predicates) do
case rest do
[] -> type(type, predicates)
_ -> {:sum, {type(type, predicates), type(rest, predicates)}}
end
end

def type(type, predicates) when is_list(predicates) do
{:type, {type, predicates}}
end
Expand Down
10 changes: 8 additions & 2 deletions lib/drops/validator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,14 @@ defmodule Drops.Validator do
{:ok, _} = success ->
success

{:error, _} ->
validate(value, type.right, path: path)
{:error, _} = left_error ->
case validate(value, type.right, path: path) do
{:ok, _} = success ->
success

{:error, _} = right_error ->
{:error, {:or, {left_error, right_error}}}
end
end
end

Expand Down
48 changes: 42 additions & 6 deletions test/contract/maybe_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ defmodule Drops.MaybeTest do
end

test "returns error with a non-string value", %{contract: contract} do
assert {:error, [{:error, {[:test], :type?, [:string, 312]}}]} =
assert {:error,
[
error:
{:or,
{{:error, {[:test], :type?, [nil, 312]}},
{:error, {[:test], :type?, [:string, 312]}}}}
]} =
contract.conform(%{test: 312})
end
end
Expand All @@ -38,12 +44,24 @@ defmodule Drops.MaybeTest do
end

test "returns error with a non-string value", %{contract: contract} do
assert {:error, [{:error, {[:test], :type?, [:string, 312]}}]} =
assert {:error,
[
{:error,
{:or,
{{:error, {[:test], :type?, [nil, 312]}},
{:error, {[:test], :type?, [:string, 312]}}}}}
]} =
contract.conform(%{test: 312})
end

test "returns error when extra predicates fail", %{contract: contract} do
assert {:error, [{:error, {[:test], :filled?, [""]}}]} =
assert {:error,
[
{:error,
{:or,
{{:error, {[:test], :type?, [nil, ""]}},
{:error, {[:test], :filled?, [""]}}}}}
]} =
contract.conform(%{test: ""})
end
end
Expand All @@ -67,7 +85,13 @@ defmodule Drops.MaybeTest do
end

test "returns error with a non-map value", %{contract: contract} do
assert {:error, [{:error, {[:test], :type?, [:map, 312]}}]} =
assert {:error,
[
{:error,
{:or,
{{:error, {[:test], :type?, [nil, 312]}},
{:error, {[:test], :type?, [:map, 312]}}}}}
]} =
contract.conform(%{"test" => 312})
end
end
Expand All @@ -88,7 +112,13 @@ defmodule Drops.MaybeTest do
end

test "returns error with a non-map value", %{contract: contract} do
assert {:error, [{:error, {[:test], :type?, [:map, 312]}}]} =
assert {:error,
[
{:error,
{:or,
{{:error, {[:test], :type?, [nil, 312]}},
{:error, {[:test], :type?, [:map, 312]}}}}}
]} =
contract.conform(%{test: 312})
end
end
Expand All @@ -114,7 +144,13 @@ defmodule Drops.MaybeTest do
end

test "returns error with a non-map value", %{contract: contract} do
assert {:error, [{:error, {[:test], :type?, [:map, 312]}}]} =
assert {:error,
[
{:error,
{:or,
{{:error, {[:test], :type?, [nil, 312]}},
{:error, {[:test], :type?, [:map, 312]}}}}}
]} =
contract.conform(%{test: 312})
end
end
Expand Down
12 changes: 10 additions & 2 deletions test/contract/rule_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,19 @@ defmodule Drops.Contract.RuleTest do

test "returns success when schema and rules passed", %{contract: contract} do
assert {:ok, %{login: "jane"}} = contract.conform(%{login: "jane", email: nil})
assert {:ok, %{email: "[email protected]"}} = contract.conform(%{login: nil, email: "[email protected]"})

assert {:ok, %{email: "[email protected]"}} =
contract.conform(%{login: nil, email: "[email protected]"})
end

test "returns predicate errors and skips rules", %{contract: contract} do
assert {:error, [{:error, {[:login], :filled?, [""]}}]} =
assert {:error,
[
error:
{:or,
{{:error, {[:login], :type?, [nil, ""]}},
{:error, {[:login], :filled?, [""]}}}}
]} =
contract.conform(%{login: "", email: nil})
end

Expand Down
65 changes: 60 additions & 5 deletions test/contract/type_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule Drops.Contract.TypeTest do
describe "type/1 with two types" do
contract do
schema do
%{required(:test) => type([:nil, :integer, :string])}
%{required(:test) => type([nil, :integer, :string])}
end
end

Expand All @@ -32,12 +32,22 @@ defmodule Drops.Contract.TypeTest do
end

test "returns error with invalid data", %{contract: contract} do
assert {:error, [{:error, {[:test], :type?, [:string, :invalid]}}]} =
assert {:error,
[
error: {
:or,
{{:error, {[:test], :type?, [nil, :invalid]}},
{:error,
{:or,
{{:error, {[:test], :type?, [:integer, :invalid]}},
{:error, {[:test], :type?, [:string, :invalid]}}}}}}
}
]} =
contract.conform(%{test: :invalid})
end
end

describe "type/1 with multiple types and extra predicates" do
describe "type/1 with multiple types and extra predicates per type" do
contract do
schema do
%{required(:test) => type([:integer, {:string, [:filled?]}])}
Expand All @@ -50,11 +60,56 @@ defmodule Drops.Contract.TypeTest do
end

test "returns error with invalid data", %{contract: contract} do
assert {:error, [{:error, {[:test], :type?, [:string, :invalid]}}]} =
assert {:error,
[
error:
{:or,
{{:error, {[:test], :type?, [:integer, :invalid]}},
{:error, {[:test], :type?, [:string, :invalid]}}}}
]} =
contract.conform(%{test: :invalid})

assert {:error, [{:error, {[:test], :filled?, [""]}}]} =
assert {:error,
[
{:error,
{:or,
{{:error, {[:test], :type?, [:integer, ""]}},
{:error, {[:test], :filled?, [""]}}}}}
]} =
contract.conform(%{test: ""})
end
end

describe "type/1 with multiple types and extra predicates for all types" do
contract do
schema do
%{required(:test) => type([:list, :map], [:filled?])}
end
end

test "returns success with valid data", %{contract: contract} do
assert {:ok, %{test: [1, 2, 3]}} = contract.conform(%{test: [1, 2, 3]})
assert {:ok, %{test: %{a: 1, b: 2}}} = contract.conform(%{test: %{a: 1, b: 2}})
end

test "returns error with invalid data", %{contract: contract} do
assert {:error,
[
error:
{:or,
{{:error, {[:test], :filled?, [[]]}},
{:error, {[:test], :type?, [:map, []]}}}}
]} =
contract.conform(%{test: []})

assert {:error,
[
error:
{:or,
{{:error, {[:test], :type?, [:list, %{}]}},
{:error, {[:test], :filled?, [%{}]}}}}
]} =
contract.conform(%{test: %{}})
end
end
end

0 comments on commit 9444154

Please sign in to comment.