defmodule Fussy.Validators.Tuple do
@behaviour Fussy.Validator
alias Fussy.Utils
alias Fussy.ValidationError
defstruct inner: nil, strict: true
@opaque t :: %__MODULE__{}
@spec new(tuple(), strict: boolean()) :: __MODULE__.t()
def new(inner, args \\ []) when is_tuple(inner) do
struct!(%__MODULE__{}, Keyword.merge(args, inner: inner))
end
def validate(%__MODULE__{} = v, term), do: validate(v, [], term)
@impl true
def validate(%__MODULE__{inner: {}}, _path, {}), do: {:ok, {}}
@impl true
def validate(%__MODULE__{inner: inner}, path, term)
when is_tuple(term) and tuple_size(inner) == tuple_size(term) do
0..(tuple_size(inner) - 1)
|> Enum.reduce({{}, []}, fn idx, {tuple, errors} ->
validator = elem(inner, idx)
item = elem(term, idx)
case Utils.validate_using(validator, path ++ [idx], item) do
{:ok, valid_item} ->
{Tuple.append(tuple, valid_item), errors}
{:error, inner_errors} ->
{tuple, errors ++ inner_errors}
end
end)
|> then(fn
{tuple, []} ->
{:ok, tuple}
{_, errors} ->
{:error, errors}
end)
end
@impl true
def validate(%__MODULE__{inner: inner, strict: false} = v, path, list)
when is_list(list) and tuple_size(inner) == length(list) do
validate(v, path, List.to_tuple(list))
end
@impl true
def validate(%__MODULE__{inner: inner}, path, term),
do:
{:error,
[
%ValidationError{
mod: __MODULE__,
msg: "must be a tuple of size #{tuple_size(inner)}",
path: path,
term: term
}
]}
end