Skip to content

Commit

Permalink
Add support for string maps
Browse files Browse the repository at this point in the history
  • Loading branch information
solnic committed Sep 12, 2023
1 parent ead3a2c commit 55cf054
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 7 deletions.
10 changes: 7 additions & 3 deletions lib/drops/contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ defmodule Drops.Contract do
end

def conform(data, %Schema{atomize: true} = schema) do
conform(Schema.atomize(data, schema), schema)
conform(Schema.atomize(data, schema.keys), schema.plan)
end

def conform(data, schema) do
results = Enum.map(schema.plan, &step(data, &1)) |> List.flatten() |> apply_rules()
def conform(data, %Schema{} = schema) do
conform(data, schema.plan)
end

def conform(data, plan) do
results = Enum.map(plan, &step(data, &1)) |> List.flatten() |> apply_rules()

if Enum.all?(results, &is_ok/1) do
{:ok, to_output(results)}
Expand Down
24 changes: 22 additions & 2 deletions lib/drops/contract/schema.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Drops.Contract.Schema do
alias __MODULE__

defstruct [:keys, :plan]
defstruct [:keys, :plan, :atomize]

defmodule Key do
defstruct [:path, :presence, :predicates, children: []]
Expand All @@ -24,9 +24,29 @@ defmodule Drops.Contract.Schema do
end

def new(map, opts) do
atomize = opts[:atomize] || false
keys = to_key_list(map)

%Schema{keys: keys, plan: build_plan(keys)}
%Schema{atomize: atomize, keys: keys, plan: build_plan(keys)}
end

def atomize(data, keys, initial \\ %{}) do
Enum.reduce(keys, initial, fn %{path: path} = key, acc ->
string_path = Enum.map(path, &Atom.to_string/1)
value = get_in(data, string_path)

updated = put_in(acc, path, value)

with_children = atomize(data, key.children, updated)
atom_part = List.delete(path, List.last(path))
string_part = List.last(string_path)

mixed_path = atom_part ++ [string_part]

{_, result} = pop_in(with_children, mixed_path)

result
end)
end

defp to_key_list(map, root \\ []) do
Expand Down
64 changes: 64 additions & 0 deletions test/contract/contract_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,68 @@ defmodule Drops.ContractTest do
contract.conform(%{name: "John"})
end
end

describe "schema for string maps" do
contract do
schema(atomize: true) do
%{
required(:user) => %{
required(:name) => type(:string, [:filled?]),
required(:age) => type(:integer),
required(:address) => %{
required(:city) => type(:string, [:filled?]),
required(:street) => type(:string, [:filled?]),
required(:zipcode) => type(:string, [:filled?])
}
}
}
end
end

test "returns success when schema validation passed", %{contract: contract} do
expected_output = %{
user: %{
name: "John",
age: 21,
address: %{
city: "New York",
street: "Central Park",
zipcode: "10001"
}
}
}

assert {:ok, output} =
contract.conform(%{
"user" => %{
"name" => "John",
"age" => 21,
"address" => %{
"city" => "New York",
"street" => "Central Park",
"zipcode" => "10001"
}
}
})

assert expected_output == output

assert {:error,
[
{:error, {:filled?, [:user, :address, :street], ""}},
{:error, {:filled?, [:user, :name], ""}}
]} =
contract.conform(%{
"user" => %{
"name" => "",
"age" => 21,
"address" => %{
"city" => "New York",
"street" => "",
"zipcode" => "10001"
}
}
})
end
end
end
6 changes: 4 additions & 2 deletions test/contract/predicates_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ defmodule Drops.PredicatesTest do
end

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

Expand All @@ -29,7 +30,8 @@ defmodule Drops.PredicatesTest do
end

test "returns error with a non-integer value", %{contract: contract} do
assert {:error, [{:error, {:integer?, [:test], "Hello"}}]} = contract.conform(%{test: "Hello"})
assert {:error, [{:error, {:integer?, [:test], "Hello"}}]} =
contract.conform(%{test: "Hello"})
end
end

Expand Down

0 comments on commit 55cf054

Please sign in to comment.