defmodule Z.Type do
@moduledoc """
A module for defining Z types
"""
@spec __using__(opts :: [options: [atom]]) :: any
defmacro __using__(opts \\ []) do
options = Keyword.get(opts, :options, [])
quote do
@behaviour Z.Type
def __z__(:type), do: __MODULE__
def __z__(:options), do: unquote(options)
def validate(input, rules \\ [], context \\ Z.Context.new(__MODULE__)) do
Z.Result.new()
|> Z.Result.set_value(input)
|> check(rules |> Z.Rule.to_keyword_list(), context)
|> Z.Result.to_tuple()
end
def validate!(input, rules \\ [], context \\ Z.Context.new(__MODULE__)) do
case validate(input, rules, context) do
{:ok, value} -> value
{:error, error} -> raise(error)
end
end
defp check(result, rules, context) do
result
|> check(:conversions, rules, context)
|> check(:type, rules, context)
|> check(:mutations, rules, context)
|> check(:assertions, rules, context)
end
defp maybe_check(result, rule, rules, context) do
if Keyword.has_key?(rules, rule) do
check(result, rule, Keyword.fetch!(rules, rule), context)
else
result
end
end
end
end
@typedoc "A Z type, primitive or custom."
@type t :: primitive | custom
@typedoc "Primitive Z types (handled by Z)."
@type primitive ::
:any
| :atom
| :boolean
| :integer
| :float
| :string
| :map
| :list
| :datetime
| :date
| :time
@typedoc "Custom types are represented by user-defined modules."
@type custom :: module
@callback check(Z.Result.t(), atom, any, Z.Context.t()) :: Z.Result.t()
@base_types %{
:any => Z.Any,
:atom => Z.Atom,
:boolean => Z.Boolean,
:integer => Z.Integer,
:float => Z.Float,
:string => Z.String,
:map => Z.Map,
:list => Z.List,
:datetime => Z.DateTime,
:date => Z.Date,
:time => Z.Time
}
def base?(type) when is_atom(type) do
Map.has_key?(@base_types, type)
end
def get(type) when is_atom(type) do
@base_types[type]
end
def resolve(type) when not is_atom(type) do
{:error, :not_an_atom}
end
def resolve(type) do
cond do
base?(type) ->
{:ok, get(type)}
Code.ensure_compiled(type) == {:module, type} ->
if function_exported?(type, :__z__, 1) do
{:ok, type}
else
{:error, :not_a_z_type}
end
true ->
{:error, :unknown_type}
end
end
end