Packages

Tools for working with data effectively - data contracts using types, schemas, domain validation rules, type-safe casting, and more.

Current section

Files

Jump to
drops lib drops types union.ex
Raw

lib/drops/types/union.ex

defmodule Drops.Types.Union do
@moduledoc ~S"""
Drops.Types.Union is a struct that represents a union type with left and right types.
## Examples
iex> Drops.Type.Compiler.visit([{:type, {:string, []}}, {:type, {:integer, []}}], [])
%Drops.Types.Union{
left: %Drops.Types.Primitive{
primitive: :string,
constraints: [predicate: {:type?, :string}]
},
right: %Drops.Types.Primitive{
primitive: :integer,
constraints: [predicate: {:type?, :integer}]
},
opts: []
}
"""
defmodule Validator do
def validate(
%{left: %{primitive: _} = left, right: %{primitive: _} = right} = type,
input
) do
case Drops.Type.Validator.validate(left, input) do
{:ok, value} ->
{:ok, value}
{:error, meta} = left_error ->
if is_list(meta) and meta[:predicate] != :type? do
left_error
else
case Drops.Type.Validator.validate(right, input) do
{:ok, value} ->
{:ok, value}
{:error, _} = right_error ->
{:error, {:or, {left_error, right_error, type.opts}}}
end
end
end
end
def validate(%{left: left, right: right} = type, input) do
case Drops.Type.Validator.validate(left, input) do
{:ok, value} ->
{:ok, value}
{:error, _} = left_error ->
case Drops.Type.Validator.validate(right, input) do
{:ok, value} ->
{:ok, value}
{:error, _} = right_error ->
{:error, {:or, {left_error, right_error, type.opts}}}
end
end
end
end
defmacro __using__(spec) do
quote do
use Drops.Type do
deftype([:left, :right])
import Drops.Types.Union
@type_spec unquote(spec)
@before_compile Drops.Types.Union
defimpl Drops.Type.Validator, for: __MODULE__ do
def validate(type, data), do: Validator.validate(type, data)
end
end
end
end
defmacro __before_compile__(_env) do
quote do
alias Drops.Type.Compiler
def new(predicates, opts) do
type = new(Keyword.merge(@opts, opts))
Map.merge(type, %{
left: constrain(type.left, predicates),
right: constrain(type.right, predicates)
})
end
def new(opts) do
{:union, {left, right}} = @type_spec
struct(__MODULE__, %{
left: Compiler.visit(left, opts),
right: Compiler.visit(right, opts),
opts: Keyword.merge(@opts, opts)
})
end
defp constrain(type, predicates) do
Map.merge(type, %{
constraints: type.constraints ++ infer_constraints(predicates)
})
end
end
end
use Drops.Type do
deftype([:left, :right])
def new(left, right) when is_struct(left) and is_struct(right) do
struct(__MODULE__, left: left, right: right)
end
end
defimpl Drops.Type.Validator, for: Union do
def validate(type, input), do: Validator.validate(type, input)
end
end