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