Packages

Data specification library inspired by clojure.spec

Current section

Files

Jump to
vow lib vow.ex
Raw

lib/vow.ex

defmodule Vow do
@moduledoc """
TODO
"""
alias Vow.{Conformable, ConformError}
@typedoc """
"""
@type t :: Conformable.t()
@doc """
"""
@spec conform(t, value :: term) :: {:ok, Conformable.conformed()} | {:error, ConformError.t()}
def conform(spec, value) do
case Conformable.conform(spec, [], [], [], value) do
{:ok, conformed} ->
{:ok, conformed}
{:error, problems} ->
{:error, ConformError.new(problems, spec, value)}
end
end
@doc """
"""
@spec conform!(t, value :: term) :: Conformable.conformed() | no_return
def conform!(spec, value) do
case conform(spec, value) do
{:ok, conformed} -> conformed
{:error, reason} -> raise reason
end
end
@doc """
"""
@spec valid?(t, value :: term) :: boolean
def valid?(spec, value) do
case conform(spec, value) do
{:ok, _} -> true
{:error, _} -> false
end
end
@doc """
"""
@spec invalid?(t, value :: term) :: boolean
def invalid?(spec, value) do
not valid?(spec, value)
end
@doc """
"""
@spec explain(t, value :: term) :: ConformError.t() | nil
def explain(spec, value) do
case conform(spec, value) do
{:ok, _} -> nil
{:error, reason} -> reason
end
end
@doc false
defmacro __using__(_opts) do
quote do
import Vow
use Vow.Func
use Vow.Ref
end
end
@doc """
"""
@spec set(Enum.t()) :: MapSet.t()
def set(enumerable), do: MapSet.new(enumerable)
@doc """
"""
@spec term?(term) :: boolean
def term?(_term), do: true
@doc """
"""
@spec any?(term) :: boolean
def any?(term), do: term?(term)
@doc """
"""
@spec also([t]) :: t
def also(specs) do
Vow.Also.new(specs)
end
@doc """
"""
@spec also(t, t) :: t
def also(spec1, spec2) do
also([spec1, spec2])
end
@doc """
"""
@spec one_of([{atom, t}, ...]) :: t | no_return
def one_of(named_specs)
when is_list(named_specs) and length(named_specs) > 0 do
Vow.OneOf.new(named_specs)
end
@doc """
"""
@spec nilable(t) :: t
def nilable(spec) do
Vow.Nilable.new(spec)
end
@typedoc """
"""
@type list_opt ::
{:length, Range.t() | non_neg_integer}
| {:min_length, non_neg_integer}
| {:max_length, non_neg_integer}
| {:distinct?, boolean}
@typedoc """
"""
@type list_opts :: [list_opt]
@doc """
"""
@spec list_of(t, list_opts) :: t
def list_of(spec, opts \\ []) do
distinct? = Keyword.get(opts, :distinct?, false)
{min, max} = get_range(opts)
Vow.List.new(spec, min, max, distinct?)
end
@doc false
@spec get_range(list_opts) :: {non_neg_integer, non_neg_integer | nil}
defp get_range(opts) do
with {:length, nil} <- {:length, Keyword.get(opts, :length)},
{:min, min} <- {:min, Keyword.get(opts, :min_length, 0)},
{:max, max} <- {:max, Keyword.get(opts, :max_length)} do
{min, max}
else
{:length, min..max} -> {min, max}
{:length, len} -> {len, len}
end
end
@doc """
"""
@spec keyword_of(t, list_opts) :: t
def keyword_of(spec, opts \\ []) do
list_of({&is_atom/1, spec}, opts)
end
@typedoc """
"""
@type map_opt ::
list_opt
| {:conform_keys?, boolean}
@typedoc """
"""
@type map_opts :: [map_opt]
@doc """
"""
@spec map_of(key_spec :: t, value_spec :: t, map_opts) :: t
def map_of(key_spec, value_spec, opts \\ []) do
distinct? = Keyword.get(opts, :distinct?, false)
conform_keys? = Keyword.get(opts, :conform_keys?, false)
{min, max} = get_range(opts)
Vow.Map.new(key_spec, value_spec, min, max, distinct?, conform_keys?)
end
@typedoc """
"""
@type merged ::
Vow.Merge.t()
| Vow.Map.t()
| Vow.Keys.t()
| map
| Vow.Alt.t()
| Vow.OneOf.t()
| Vow.Cat.t()
@doc """
"""
@spec merge([merged], (key, value, value -> value) | nil) :: t when key: term, value: term
def merge(specs, merge_fun \\ nil) do
Vow.Merge.new(specs, merge_fun)
end
@typedoc """
"""
@type spec_ref :: atom | {module, atom} | Vow.Ref.t()
@typedoc """
"""
@type spec_ref_expr ::
spec_ref
| {:and | :or, [spec_ref_expr, ...]}
@typedoc """
"""
@type key_opt ::
{:required, [spec_ref_expr]}
| {:optional, [spec_ref_expr]}
| {:into, [] | %{}}
@typedoc """
"""
@type key_opts :: [key_opt]
@doc """
"""
@spec keys(key_opts) :: t | no_return
def keys(opts) do
Vow.Keys.new(opts)
end
@doc """
"""
@spec regex?(t) :: boolean
def regex?(spec) do
case Vow.RegexOperator.impl_for(spec) do
nil -> false
_ -> true
end
end
@doc """
"""
@spec amp([t]) :: t
def amp(specs) do
Vow.Amp.new(specs)
end
@doc """
"""
@spec amp(t, t) :: t
def amp(spec1, spec2) do
amp([spec1, spec2])
end
@doc """
"""
@spec maybe(t) :: t
def maybe(spec) do
Vow.Maybe.new(spec)
end
@doc """
"""
@spec one_or_more(t) :: t
def one_or_more(spec) do
Vow.OneOrMore.new(spec)
end
@doc """
"""
@spec oom(t) :: t
def oom(spec), do: one_or_more(spec)
@doc """
"""
@spec zero_or_more(t) :: t
def zero_or_more(spec) do
Vow.ZeroOrMore.new(spec)
end
@doc """
"""
@spec zom(t) :: t
def zom(spec), do: zero_or_more(spec)
@doc """
"""
@spec alt([{atom, t}, ...]) :: t | no_return
def alt(named_specs)
when is_list(named_specs) and length(named_specs) > 0 do
Vow.Alt.new(named_specs)
end
@doc """
"""
@spec cat([{atom, t}, ...]) :: t | no_return
def cat(named_specs)
when is_list(named_specs) and length(named_specs) > 0 do
Vow.Cat.new(named_specs)
end
@typedoc """
"""
@type fspec_opt ::
{:args, [t]}
| {:ret, t}
| {:fun, t}
@typedoc """
"""
@type fspec_opts :: [fspec_opt]
@doc """
"""
@spec fspec(fspec_opts) :: t
def fspec(opts) do
args = Keyword.get(opts, :args)
ret = Keyword.get(opts, :ret)
fun = Keyword.get(opts, :fun)
Vow.Function.new(args, ret, fun)
end
defdelegate conform_function(spec, function, args \\ []), to: Vow.Function, as: :conform
end