Skip to content

Commit

Permalink
Merge pull request #39 from solnic/support-custom-map-types
Browse files Browse the repository at this point in the history
Add support for defining custom map types

Refs #37
  • Loading branch information
solnic authored Jan 25, 2024
2 parents 41c85ed + d1d06e0 commit 6b6e2e8
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 10 deletions.
18 changes: 18 additions & 0 deletions lib/drops/type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule Drops.Type do
"""

alias __MODULE__
alias Drops.Type.Compiler
alias Drops.Types.Map.Key

defmacro __using__(do: block) do
quote do
Expand All @@ -14,6 +16,20 @@ defmodule Drops.Type do
end
end

defmacro __using__({:%{}, _, _} = spec) do
quote do
import Drops.Type
import Drops.Type.DSL

keys =
Enum.map(unquote(spec), fn {{presence, name}, type_spec} ->
%Key{path: [name], presence: presence, type: Compiler.visit(type_spec, [])}
end)

use Drops.Types.Map, keys: keys
end
end

defmacro __using__(spec) do
quote do
import Drops.Type
Expand Down Expand Up @@ -82,10 +98,12 @@ defmodule Drops.Type do
end

def infer_primitive([]), do: :any
def infer_primitive(map) when is_map(map), do: :map
def infer_primitive(name) when is_atom(name), do: name
def infer_primitive({:type, {name, _}}), do: name

def infer_constraints([]), do: []
def infer_constraints(map) when is_map(map), do: []
def infer_constraints(type) when is_atom(type), do: [predicate(:type?, [type])]

def infer_constraints(predicates) when is_list(predicates) do
Expand Down
43 changes: 33 additions & 10 deletions lib/drops/types/map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,11 @@ defmodule Drops.Types.Map do
"""

alias __MODULE__
alias Drops.Predicates
alias Drops.Types.Map.Key

use Drops.Type do
deftype(:map, keys: [], atomize: false)

def new(keys, opts) when is_list(keys) do
atomize = opts[:atomize] || false
struct(__MODULE__, keys: keys, atomize: atomize)
end
end

defimpl Drops.Type.Validator, for: Map do
defmodule Validator do
def validate(%{atomize: true, keys: keys} = type, data) do
case Predicates.Helpers.apply_predicates(Map.atomize(data, keys), type.constraints) do
{:ok, result} ->
Expand Down Expand Up @@ -84,6 +76,37 @@ defmodule Drops.Types.Map do
end
end

defmacro __using__(opts) do
quote do
use Drops.Type do
deftype(:map, keys: unquote(opts[:keys]), atomize: false)

import Drops.Types.Map

def new(opts) do
struct(__MODULE__, opts)
end

defimpl Drops.Type.Validator, for: __MODULE__ do
def validate(type, data), do: Validator.validate(type, data)
end
end
end
end

use Drops.Type do
deftype(:map, keys: [], atomize: false)
end

defimpl Drops.Type.Validator, for: Map do
def validate(type, data), do: Validator.validate(type, data)
end

def new(keys, opts) when is_list(keys) do
atomize = opts[:atomize] || false
struct(__MODULE__, keys: keys, atomize: atomize)
end

def atomize(data, keys, initial \\ %{}) do
Enum.reduce(keys, initial, fn %{path: path} = key, acc ->
stringified_key = Key.stringify(key)
Expand Down
27 changes: 27 additions & 0 deletions test/contract/types/custom_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,31 @@ defmodule Drops.Contract.Types.CustomTest do
assert_errors(["test must be filled"], contract.conform(%{test: ""}))
end
end

describe "using a custom map type" do
defmodule User do
use Drops.Type, %{
required(:name) => string()
}
end

contract do
schema do
%{required(:user) => User}
end
end

test "returns success with a valid input", %{contract: contract} do
assert {:ok, %{user: %{name: "John"}}} = contract.conform(%{user: %{name: "John"}})
end

test "returns errors with invalid input", %{contract: contract} do
assert_errors(["user must be a map"], contract.conform(%{user: 312}))

assert_errors(
["user.name must be a string"],
contract.conform(%{user: %{name: 312}})
)
end
end
end

0 comments on commit 6b6e2e8

Please sign in to comment.