Skip to content

Commit

Permalink
wip wip
Browse files Browse the repository at this point in the history
  • Loading branch information
solnic committed Dec 11, 2023
1 parent 12f8531 commit f7e6d2d
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 48 deletions.
67 changes: 34 additions & 33 deletions lib/drops/contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ defmodule Drops.Contract do
end

def conform(data, %Types.Map{} = schema, path: path) do
results = Drops.Type.Validator.validate(schema, data)
output = to_output(results, %{})
errors = Enum.reject(results, &is_ok/1)
{outcome, {:map, items}} = result = Drops.Type.Validator.validate(schema, data)

output = to_output(result)
errors = if outcome == :ok, do: [], else: Enum.reject(items, &is_ok/1)

all_errors = if Enum.empty?(path), do: errors ++ apply_rules(output), else: errors

Expand Down Expand Up @@ -289,24 +290,24 @@ defmodule Drops.Contract do
end
end

def to_output({:list, results}) do
Enum.map(results, &to_output/1)
def to_output({_, {:map, [head | tail]}}) do
to_output(tail, to_output(head, %{}))
end

def to_output({:ok, {:list, results}}) do
Enum.map(results, &to_output/1)
def to_output({_, {:list, results}}) do
to_output(results)
end

def to_output([head | tail]) do
if Enum.empty?(tail) do
to_output(head)
else
[to_output(head) | to_output(tail)]
end
def to_output({:list, results}) do
to_output(results)
end

def to_output({:ok, {path, value}}) do
put_in(%{}, Enum.map(path, &Access.key(&1, %{})), to_output(value))
def to_output({:map, results}) do
to_output(results, %{})
end

def to_output([head | tail]) do
[to_output(head) | to_output(tail)]
end

def to_output({:ok, value}) do
Expand All @@ -317,29 +318,29 @@ defmodule Drops.Contract do
value
end

def to_output([head | tail], output) do
case head do
{:ok, {path, results}} when is_list(results) ->
to_output(
tail,
put_in(output, Enum.map(path, &Access.key(&1, %{})), to_output(results))
)
def to_output(:ok, output) do
output
end

{:ok, {path, value}} ->
to_output(
tail,
put_in(output, Enum.map(path, &Access.key(&1, %{})), to_output(value))
)
def to_output([], output) do
output
end

:ok ->
to_output(tail, output)
def to_output({:ok, {path, result}}, output) do
put_in(output, Enum.map(path, &Access.key(&1, %{})), to_output(result))
end

{:error, _} ->
to_output(tail, output)
end
def to_output({:error, _}, output) do
output
end

def to_output([], output), do: output
def to_output({:list, results}, output) do
to_output(results, output)
end

def to_output([head | tail], output) do
to_output(tail, to_output(head, output))
end

defp set_schema(_caller, name, opts, block) do
quote do
Expand Down
20 changes: 18 additions & 2 deletions lib/drops/types/map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ defmodule Drops.Types.Map do
def validate(%{atomize: true, keys: keys} = type, data) do
case apply_predicates(Map.atomize(data, keys), type.constraints) do
{:ok, result} ->
Enum.map(type.keys, &Key.validate(&1, result)) |> List.flatten()
results = Enum.map(type.keys, &Key.validate(&1, result)) |> List.flatten()
errors = Enum.reject(results, &is_ok/1)

if Enum.empty?(errors),
do: {:ok, {:map, results}},
else: {:error, {:map, results}}

{:error, errors} ->
{:error, errors}
Expand All @@ -63,7 +68,12 @@ defmodule Drops.Types.Map do
def validate(type, data) do
case apply_predicates(data, type.constraints) do
{:ok, result} ->
Enum.map(type.keys, &Key.validate(&1, result)) |> List.flatten()
results = Enum.map(type.keys, &Key.validate(&1, result)) |> List.flatten()
errors = Enum.reject(results, &is_ok/1)

if Enum.empty?(errors),
do: {:ok, {:map, results}},
else: {:error, {:map, results}}

{:error, errors} ->
{:error, errors}
Expand Down Expand Up @@ -96,6 +106,12 @@ defmodule Drops.Types.Map do
defp apply_predicate(_, {:error, _} = error) do
error
end

defp is_ok(results) when is_list(results), do: Enum.all?(results, &is_ok/1)
defp is_ok(:ok), do: true
defp is_ok({:ok, _}), do: true
defp is_ok(:error), do: false
defp is_ok({:error, _}), do: false
end

def atomize(data, keys, initial \\ %{}) do
Expand Down
14 changes: 12 additions & 2 deletions lib/drops/validator/messages/backend.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ defmodule Drops.Validator.Messages.Backend do
%Drops.Validator.Messages.Error.Type{
path: [:email],
text: "312 received but it must be a string",
meta: %{args: [:string, 312], predicate: :type?}
meta: [predicate: :type?, args: [:string, 312]]
},
%Drops.Validator.Messages.Error.Type{
path: [:name],
text: "cannot be empty",
meta: %{args: [""], predicate: :filled?}
meta: [predicate: :filled?, args: [""]]
}
]
}
Expand Down Expand Up @@ -92,6 +92,15 @@ defmodule Drops.Validator.Messages.Backend do
%Error.Type{path: path, text: text(predicate, input), meta: meta}
end

defp error({:error, {path, {:map, errors}}}) do
Error.Conversions.nest(
%Error.Set{
errors: Enum.reject(Enum.map(errors, &error/1), &is_nil/1)
},
path
)
end

defp error({:error, {path, {:list, results}}}) when is_list(results) do
errors = Enum.map(results, &error/1) |> Enum.reject(&is_nil/1)
if Enum.empty?(errors), do: nil, else: %Error.Set{errors: errors}
Expand All @@ -117,6 +126,7 @@ defmodule Drops.Validator.Messages.Backend do
%Error.Caster{error: error({:error, {path, error}})}
end

defp error(:ok), do: nil
defp error({:ok, _}), do: nil
end
end
Expand Down
22 changes: 11 additions & 11 deletions test/contract/messages_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defmodule Drops.Validator.MessagesTest do
contract.conform(%{name: "Jane Doe"})

assert path == [:age]
assert meta == %{predicate: :has_key?, args: [:age]}
assert meta == [predicate: :has_key?, args: []]
assert to_string(error) == "age key must be present"
end
end
Expand All @@ -40,7 +40,7 @@ defmodule Drops.Validator.MessagesTest do
contract.conform(%{name: "Jane Doe", age: "twenty"})

assert path == [:age]
assert meta == %{predicate: :type?, args: [:integer, "twenty"]}
assert meta == [predicate: :type?, args: [:integer, "twenty"]]
assert to_string(error) == "age must be an integer"
end

Expand All @@ -49,7 +49,7 @@ defmodule Drops.Validator.MessagesTest do
contract.conform(%{name: "", age: 21})

assert path == [:name]
assert meta == %{predicate: :filled?, args: [""]}
assert meta == [predicate: :filled?, args: [""]]
assert to_string(error) == "name must be filled"
end

Expand All @@ -58,7 +58,7 @@ defmodule Drops.Validator.MessagesTest do
contract.conform(%{name: "Jane", age: 12})

assert path == [:age]
assert meta == %{predicate: :gt?, args: [18, 12]}
assert meta == [predicate: :gt?, args: [18, 12]]
assert to_string(error) == "age must be greater than 18"
end

Expand All @@ -67,7 +67,7 @@ defmodule Drops.Validator.MessagesTest do
contract.conform(%{name: "Jane", age: 19, role: "oops"})

assert path == [:role]
assert meta == %{predicate: :in?, args: [["admin", "user"], "oops"]}
assert meta == [predicate: :in?, args: [["admin", "user"], "oops"]]
assert to_string(error) == "role must be one of: admin, user"
end

Expand All @@ -76,10 +76,10 @@ defmodule Drops.Validator.MessagesTest do
contract.conform(%{birthday: "oops"})

assert left_error.path == [:birthday]
assert left_error.meta == %{predicate: :type?, args: [nil, "oops"]}
assert left_error.meta == [predicate: :type?, args: [nil, "oops"]]

assert right_error.path == [:birthday]
assert right_error.meta == %{predicate: :type?, args: [:date, "oops"]}
assert right_error.meta == [predicate: :type?, args: [:date, "oops"]]

assert to_string(error) == "birthday must be nil or birthday must be a date"
end
Expand All @@ -99,20 +99,20 @@ defmodule Drops.Validator.MessagesTest do
end

test "returns errors from a type? predicate", %{contract: contract} do
assert {:error, [error = %{path: path, meta: meta}]} =
assert {:error, [%{errors: [error = %{path: path, meta: meta}]}]} =
contract.conform(%{user: %{age: "twenty"}})

assert path == [:user, :age]
assert meta == %{predicate: :type?, args: [:integer, "twenty"]}
assert meta == [predicate: :type?, args: [:integer, "twenty"]]
assert to_string(error) == "user.age must be an integer"
end

test "returns errors from a list type", %{contract: contract} do
assert {:error, [error = %{path: path, meta: meta}]} =
assert {:error, [%{errors: [%{errors: [error = %{path: path, meta: meta}]}]}]} =
contract.conform(%{user: %{roles: ["admin", 312, "moderator"]}})

assert path == [:user, :roles, 1]
assert meta == %{predicate: :type?, args: [:string, 312]}
assert meta == [predicate: :type?, args: [:string, 312]]
assert to_string(error) == "user.roles.1 must be a string"
end
end
Expand Down

0 comments on commit f7e6d2d

Please sign in to comment.